--- /dev/null
+Order Deny,Allow
--- /dev/null
+Copyright Roger Bystroem <roger@remiss.org>
+Released under GPL v.2
+
+This applies to everything not specified otherwise in CREDITS or elsewhere
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
--- /dev/null
+CREDITS
+
+Icons stolen from (art.gnome.org):
+ Silvestre Herrera (Nuovo images)
+ Steven Garrity (Tango, see tango authors)
+
+media-*
+ -Gnome Default Theme
+
+Albumart from amazon, copyrights belong to their respective copyright-holders
+
+Net_MPD (http://pear.php.net/package/Net_MPD/):
+ Graham Christensen
+
+Jorbis (used for ogg playback)
+http://www.jcraft.com/jorbis/
--- /dev/null
+
+Pitchfork ChangeLog
+
+------------------------------------------------------------------------------
+
+Changes 0.5.5 (18.01.2008)
+ * pitchfork now reports errors from mpd
+ * New thexy theme from Gianni Chiappetta
+
+Changes - 0.5.4 (25.09.2007)
+ * Cleanup css files so it's easier to make new themes
+ * Plausible fix for segfault/unable to assign complex types to properties
+ issues with albumart
+ * Menu options finally look decent
+ * Make dirlist init not fail if library is empty
+ * Removed a few useless debug statements
+ * Tried to switch to a negative page when pagination was enabled, fix for #81
+ * Typo in multi add made it only search for last parameter, found by oliver
+ * French translation by Djanil
+ * Basque translation by Xabier Ezpeleta
+ * Fix for slow loading of page one when there's a big playlist (#86)
+ * Switched to html controls for streaming applet and also improved the applet
+ a bit
+
+Changes - 0.5.3 (24.06.2007)
+ * Improved amazon album search to give more correct hit
+ * Added more to album name as described in #62
+ * Show title under artist/album browser instead of filename
+ * Show Artist - Title in browser title bar
+ * Fix bug with xml2array function causing lyrics to loose newlines
+ * Make streaming applet autoplay again and also when coming out of hiding
+ * Make applet stop playing immediately when mpd stops playing to make it seem
+ more responsive
+ * Fix race condition with sidebar and album art
+ * Update big albumart when it's open
+ * Translation support
+ * Show full file path with search results
+ * Make the player control area slightly shorter
+ * German translation by Christian Fischer
+
+
+Changes - 0.5.2 (24.04.2007)
+ * Support for changing position display to remaining
+ * Workaround for konqueror repaint issue (bug #46)
+ * Added pagination support
+ * Added ability to search playlist
+ * Short info/test display on configure screen
+ * Follow and jump to currently playing song option
+ * Added dark theme
+ * Encrypt/hash pitchfork login password
+ * Fixed bug in Hashmap.remove. It would return the container object if it was
+ the first element in the list.
+ * Added nofollow and noindex to html meta tags. Consider adding pitchfork to
+ a robots.txt file on your server.
+ * Fix caching issue in Opera on the stream player
+ * Add background property to the stream plugin and try to close audio device
+ when stream stops
+ * Workaround for broken xml-declaration on lyricwiki.org
+ * Link to adding/editing lyric on lyricwiki.org
+
+
+Changes - 0.5.1 (21.03.2007)
+ * Switched to JSON in commands
+ * Added ogg streaming support with jorbisplayer
+ * Improved speed of plchanges
+ * Add warnings when something goes wrong with locking of metadata-files
+ * Don't use PHP shorttags
+ * Cleanups
+ * Fix bug where albumart wouldn't show when mime_content_type wasn't
+ available
+ * Added license blurp to all files to avoid any issues
+
+
+Changes - 0.5.0 (28.02.2007)
+ * Improved theming support
+ * Add theme choice to configure screen
+ * Album review/description
+ * Fix MPD port specification bug
+ * Fix bug with filename in lyrics files
+ * Add option for including stop button
+ * Clean up include stuff (also fixes an issue with initial config screen in opera)
+ * Fix font-issues
+ * Allow to change what should be displayed in the playlist
+ * Add forced lyrics reload
+ * Fixes for safari specific issues
+ * Metadata for album review and description
+ * Changed look of lyrics screen and added refetch button
+
+Changes - 0.4.1 (10.02.2007)
+ * Minor threshold on move in playlist
+ * Fix bug in directory browser
+ * Playlistsupport
+ * Optimizations for selection and playlist
+ * Test on notfound value in album.txt and try again if it is some while ago
+ * adding playlists
+
+Changes - 0.4.0 (08.02.2007)
+ * multiple move support
+ * speed up plchanges
+ * Get changes based only on id/pos
+ * Update DB support
+ * Bugfixes on directory browser
+ * Autoplay on add if pl empty
+ * Search function
+
+Changes - 0.3.1 (07.02.2007)
+ * Fix rendering error in browsers other than gecko-based
+
+Changes - 0.3.0 (04.02.2007)
+ * Added parent when doubleclicked in dirlist
+ * Don't add whole directory if return is pressed in qa and nothing there
+ * album art
+ * lyrics
+ * mv TODO ChangeLog
+ * new_state == stop => remove currently playing
+ * config => password protection, delay time
+ * Directory browsing based on artist etc
+ * PHP to javascript preferences (delay time won't be implemented until this is fixed)
+ * Split css into base/color-stuff
+
+Changes - 0.2.0 (29.01.2007)
+ * Pool for playlist elements as gecko don't seem to free them
+ * server settings (output devices, random, repeat, xfade) drop-down menu
+ * close folder if clicking on folder icon when folder is open
+ * Speedup of playlist changes and initial playlist loading
+ * Searching in directory listing
+ * Close can't be on escape, some browsers closes open connections to server
+ * Adjust slider when clicking somewhere on it
+ * Run slider callback when moving (250ms threshold)
+ * Fix race-condition, dirlist started building before elements were ready
+ * Several concurrent information requests, should never be more than one looping....
+ * reconnect on broken set_timeout
+ * Quick Add
+ * Clean upp all display data when clearing playlist/stopping
+
+Changes - 0.1.0rc1/2 (21.01.2007)
+ * icon to indicate song/folder in dirlist
+ * organization
+ - double click moves right, seperate enque button (hot key for add)
+ - double click adds, single click opens
+ * .htaccess/stuff.conf file
+ * Use utf-8
+ * When removing song before currently playing, showing of currently playing is removed
+ * Hi-light on prev/play/pause/next ..done
+ * coloured background around the "player" area (mockup.png)
+ * seems to fail on selecting playing song sometimes
+ * fixed position on "player" area?
+ * moving something up in the list causes jumping on response from server (the moved is probably placed wrongly so that it jumps one up on response from server)
+ - "Fixed" - when dropped on something it probably should land there....
+ - FIXED! Oh happy days!
--- /dev/null
+
+1. Installation
+ see doc/INSTALL
+
+2. Requirements
+ * PHP 5.1.3 or newer
+ * PHP-Pear
+ * DOM2 capable browser (firefox, konqueror, opera, safari, etc)
+ * MPD 0.12.0 or later
+
+
+3. Shortcuts
+ Key shortcuts in browsers are a mess, some don't send on certain keys and others
+ don't allow you to cancel default action, therefore the shortcuts are a little weird.
+ I've tried to use what seems most logical and works in most browsers, that also means
+ there's a slight workaround some places
+
+ - Previous song: j
+ - Pause/Play: k
+ - Next song: l
+ - Close directory: Ctrl+Shift+X
+ - Quick search: Ctrl+Shift+S or Ctrl+Alt+S
+ - Jumpt to current song: space
--- /dev/null
+Order Allow,Deny
--- /dev/null
+config.xml
+metadata
--- /dev/null
+Installation instructions
+
+1. Put either the pitchfork.conf or pitchfork_domain.conf file, found in this doc/ directory,
+ in /etc/http/conf.d (or where your extra config-files are)
+ and make sure they specify the correct directories for pitchfork.
+2. Apache needs to have write access to the config directory:
+ - Go to where you installed pitchfork
+ - chown apache config (or whatever user apache is run as, can be 'nobody')
+ to find out who the apache-user is check output from "ps auxww | grep httpd || ps auxww|grep apache"
+3. Reload/restart apache
+4. Open your web-browser to where you installed apache and answer the questions ;)
+
+Apache access logs
+The client by default requests information every second from the server, this means that a lot of
+stuff will end up in your access log. To disable logging of the these requests you will have to
+change the CustomLog directive in apache. It should be sufficient to add env=!pitchforknolog after it
+(if you have used the given .conf file)
+
+Example customlog directive:
+CustomLog logs/access_log combined env=!pitchforknolog
+
+For more information about logs:
+http://httpd.apache.org/docs/2.0/logs.html#accesslog
+
+Required PHP-Modules:
+Usually shipped with the php-distribution, but listed here for modular php-implementations.
+ - Hash
+ - SimpleXML
+ - JSON (not required, but highly recommended)
+
+
+When you have problems:
+
+ * apache refuses to start and you see "invalid command php_flag"
+ * The only thing you see is some PHP-code
+ - You have to enable PHP in httpd.conf, make sure you have
+ "LoadModule php5_module modules/libphp5.so" somewhere in the config file
+
+ * All you see is a blank page OR
+ * Warning: require_once(PEAR/Exception.php) [function.require-once]:
+ failed to open stream: No such file or
+ directory in <something>/pitchfork/inc/Net/MPD.php
+ - You need to install php-pear (might also be called PEAR-PEAR)
+
+ * Allowed memory size of .. bytes exhausted in apache error log
+ - You need to increase the allowed memory size in php.ini,
+ set memory_limit to at least 64M
+
+ * No metadata (ie. images/lyrics/etc) is showing
+ - make sure allow_url_fopen is set to on in php.ini and that the config/ directory
+ is writeable by apache
+
+
--- /dev/null
+
+Should be sufficient to just extract the tarball and copy the old
+config directory over to the new directory. (cp -A pitchfork.old/config/* pitchfork/config/)
+
+Remember to make sure apache has write permission to the config directory.
+
+
--- /dev/null
+Alias /pitchfork /var/www/pitchfork
+<Directory /var/www/pitchfork>
+ DirectoryIndex index.php
+ Options -Indexes
+ AllowOverride all
+ AddType application/x-httpd-php .php
+ php_flag magic_quotes_gpc off
+ php_flag magic_quotes_runtime off
+ php_flag display_errors off
+ php_flag log_errors on
+ # switch comment marks on the two items to disable access logging
+ # completely for pitchfork. See INSTALL
+ SetEnvIf Request_URI "player/command\.php" pitchforknolog
+ #SetEnv pitchforknolog
+</Directory>
--- /dev/null
+<VirtualHost *:80>
+ # you will probably want to change the two following settings:
+ ServerName pitchfork
+ DocumentRoot /var/www/pitchfork
+ <Directory /var/www/pitchfork>
+ AllowOverride all
+ </Directory>
+ DirectoryIndex index.php
+ Options -Indexes
+ php_flag magic_quotes_gpc off
+ php_flag display_errors off
+ php_flag log_errors on
+ AddType application/x-httpd-php .php
+ # switch comment marks on the two items to disable access logging
+ # completely for pitchfork. See INSTALL
+ SetEnvIf Request_URI "player/command\.php" pitchforknolog
+ #SetEnv pitchforknolog
+</VirtualHost>
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package Services_JSON
+ * @author Michal Migurski <mike-json@teczno.com>
+ * @author Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright 2005 Michal Migurski
+ * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE', 1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR', 2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR', 3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ', 4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+ /**
+ * constructs a new JSON instance
+ *
+ * @param int $use object behavior flags; combine with boolean-OR
+ *
+ * possible values:
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
+ * "{...}" syntax creates associative arrays
+ * instead of objects in decode().
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
+ * Values which can't be encoded (e.g. resources)
+ * appear as NULL instead of throwing errors.
+ * By default, a deeply-nested resource will
+ * bubble up with an error, so all return values
+ * from encode() should be checked with isError()
+ */
+ function Services_JSON($use = 0)
+ {
+ $this->use = $use;
+ }
+
+ /**
+ * convert a string from one UTF-16 char to one UTF-8 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf16 UTF-16 character
+ * @return string UTF-8 character
+ * @access private
+ */
+ function utf162utf8($utf16)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+ }
+
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+ switch(true) {
+ case ((0x7F & $bytes) == $bytes):
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x7F & $bytes);
+
+ case (0x07FF & $bytes) == $bytes:
+ // return a 2-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
+ . chr(0x80 | ($bytes & 0x3F));
+
+ case (0xFFFF & $bytes) == $bytes:
+ // return a 3-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
+ . chr(0x80 | ($bytes & 0x3F));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * convert a string from one UTF-8 char to one UTF-16 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ * @access private
+ */
+ function utf82utf16($utf8)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch(strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8{0}) >> 2))
+ . chr((0xC0 & (ord($utf8{0}) << 6))
+ | (0x3F & ord($utf8{1})));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8{0}) << 4))
+ | (0x0F & (ord($utf8{1}) >> 2)))
+ . chr((0xC0 & (ord($utf8{1}) << 6))
+ | (0x7F & ord($utf8{2})));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+
+ /* modified for pitchfork to use echo instead of recursing over all elements and returning a *large* string */
+ /* could probably remove the conversion thing as input is utf-8 and output is utf-8 */
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access public
+ */
+ function encode($var)
+ {
+ switch (gettype($var)) {
+ case 'boolean':
+ echo $var ? 'true' : 'false';
+ return;
+
+ case 'NULL':
+ echo 'null';
+ return;
+
+ case 'integer':
+ echo (int) $var;
+ return;
+
+ case 'double':
+ case 'float':
+ echo (float) $var;
+ return;
+
+ case 'string':
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+ $ascii = '';
+ $strlen_var = strlen($var);
+
+ /*
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for ($c = 0; $c < $strlen_var; ++$c) {
+
+ $ord_var_c = ord($var{$c});
+
+ switch (true) {
+ case $ord_var_c == 0x08:
+ $ascii .= '\b';
+ break;
+ case $ord_var_c == 0x09:
+ $ascii .= '\t';
+ break;
+ case $ord_var_c == 0x0A:
+ $ascii .= '\n';
+ break;
+ case $ord_var_c == 0x0C:
+ $ascii .= '\f';
+ break;
+ case $ord_var_c == 0x0D:
+ $ascii .= '\r';
+ break;
+
+ case $ord_var_c == 0x22:
+ case $ord_var_c == 0x2F:
+ case $ord_var_c == 0x5C:
+ // double quote, slash, slosh
+ $ascii .= '\\'.$var{$c};
+ break;
+
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $var{$c};
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $c += 1;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}));
+ $c += 2;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}));
+ $c += 3;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFC) == 0xF8):
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}));
+ $c += 4;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFE) == 0xFC):
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}),
+ ord($var{$c + 5}));
+ $c += 5;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+ }
+ }
+
+ echo '"'.$ascii.'"';
+ return;
+
+ case 'array':
+ /*
+ * As per JSON spec if any array key is not an integer
+ * we must treat the the whole array as an object. We
+ * also try to catch a sparsely populated associative
+ * array with numeric keys here because some JS engines
+ * will create an array with empty indexes up to
+ * max_index which can cause memory issues and because
+ * the keys, which may be relevant, will be remapped
+ * otherwise.
+ *
+ * As per the ECMA and JSON specification an object may
+ * have any string as a property. Unfortunately due to
+ * a hole in the ECMA specification if the key is a
+ * ECMA reserved word or starts with a digit the
+ * parameter is only accessible using ECMAScript's
+ * bracket notation.
+ */
+
+ // treat as a JSON object
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+ echo "{";
+ $first = true;
+ foreach($var as $k => $v) {
+ if($first) $first = false;
+ else echo ",";
+ $this->name_value($k, $v);
+ }
+
+ echo '}';
+ return;
+ }
+
+ // treat it like a regular array
+ echo "[";
+ $first = true;
+ foreach($var as $e) {
+ if($first) $first = false;
+ else echo ",";
+ $this->encode($e);
+ }
+ echo "]";
+ return;
+
+ case 'object':
+ echo "{";
+ $first = true;
+ foreach($var as $k => $v) {
+ if($first)
+ $first = false;
+ else echo ",";
+ $this->name_value($k, $v);
+ }
+
+ echo '}';
+ return;
+
+ default:
+ echo ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+ ? 'null'
+ : 'error encoding';
+ return;
+ }
+ }
+
+ /**
+ * array-walking function for use in generating JSON-formatted name-value pairs
+ *
+ * @param string $name name of key to use
+ * @param mixed $value reference to an array element to be encoded
+ *
+ * @return string JSON-formatted name-value pair, like '"name":value'
+ * @access private
+ */
+ function name_value($name, $value)
+ {
+ $this->encode(strval($name));
+ echo ':';
+ $this->encode($value);
+ }
+
+ /**
+ * reduce a string by removing leading and trailing comments and whitespace
+ *
+ * @param $str string string value to strip of comments and whitespace
+ *
+ * @return string string value stripped of comments and whitespace
+ * @access private
+ */
+ function reduce_string($str)
+ {
+ $str = preg_replace(array(
+
+ // eliminate single line comments in '// ...' form
+ '#^\s*//(.+)$#m',
+
+ // eliminate multi-line comments in '/* ... */' form, at start of string
+ '#^\s*/\*(.+)\*/#Us',
+
+ // eliminate multi-line comments in '/* ... */' form, at end of string
+ '#/\*(.+)\*/\s*$#Us'
+
+ ), '', $str);
+
+ // eliminate extraneous space
+ return trim($str);
+ }
+
+ /**
+ * decodes a JSON string into appropriate variable
+ *
+ * @param string $str JSON-formatted string
+ *
+ * @return mixed number, boolean, string, array, or object
+ * corresponding to given JSON input string.
+ * See argument 1 to Services_JSON() above for object-output behavior.
+ * Note that decode() always returns strings
+ * in ASCII or UTF-8 format!
+ * @access public
+ */
+ function decode($str)
+ {
+ $str = $this->reduce_string($str);
+
+ switch (strtolower($str)) {
+ case 'true':
+ return true;
+
+ case 'false':
+ return false;
+
+ case 'null':
+ return null;
+
+ default:
+ $m = array();
+
+ if (is_numeric($str)) {
+ // Lookie-loo, it's a number
+
+ // This would work on its own, but I'm trying to be
+ // good about returning integers where appropriate:
+ // return (float)$str;
+
+ // Return float or int, as appropriate
+ return ((float)$str == (integer)$str)
+ ? (integer)$str
+ : (float)$str;
+
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+ // STRINGS RETURNED IN UTF-8 FORMAT
+ $delim = substr($str, 0, 1);
+ $chrs = substr($str, 1, -1);
+ $utf8 = '';
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+ $ord_chrs_c = ord($chrs{$c});
+
+ switch (true) {
+ case $substr_chrs_c_2 == '\b':
+ $utf8 .= chr(0x08);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\t':
+ $utf8 .= chr(0x09);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\n':
+ $utf8 .= chr(0x0A);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\f':
+ $utf8 .= chr(0x0C);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\r':
+ $utf8 .= chr(0x0D);
+ ++$c;
+ break;
+
+ case $substr_chrs_c_2 == '\\"':
+ case $substr_chrs_c_2 == '\\\'':
+ case $substr_chrs_c_2 == '\\\\':
+ case $substr_chrs_c_2 == '\\/':
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+ $utf8 .= $chrs{++$c};
+ }
+ break;
+
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+ // single, escaped unicode character
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
+ $utf8 .= $this->utf162utf8($utf16);
+ $c += 5;
+ break;
+
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+ $utf8 .= $chrs{$c};
+ break;
+
+ case ($ord_chrs_c & 0xE0) == 0xC0:
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 2);
+ ++$c;
+ break;
+
+ case ($ord_chrs_c & 0xF0) == 0xE0:
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 3);
+ $c += 2;
+ break;
+
+ case ($ord_chrs_c & 0xF8) == 0xF0:
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 4);
+ $c += 3;
+ break;
+
+ case ($ord_chrs_c & 0xFC) == 0xF8:
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 5);
+ $c += 4;
+ break;
+
+ case ($ord_chrs_c & 0xFE) == 0xFC:
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 6);
+ $c += 5;
+ break;
+
+ }
+
+ }
+
+ return $utf8;
+
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+ // array, or object notation
+
+ if ($str{0} == '[') {
+ $stk = array(SERVICES_JSON_IN_ARR);
+ $arr = array();
+ } else {
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = array();
+ } else {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = new stdClass();
+ }
+ }
+
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
+ 'where' => 0,
+ 'delim' => false));
+
+ $chrs = substr($str, 1, -1);
+ $chrs = $this->reduce_string($chrs);
+
+ if ($chrs == '') {
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } else {
+ return $obj;
+
+ }
+ }
+
+ //print("\nparsing {$chrs}\n");
+
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+ $top = end($stk);
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+ // found a comma that is not inside a string, array, etc.,
+ // OR we've reached the end of the character list
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ // we are in an array, so just push an element onto the stack
+ array_push($arr, $this->decode($slice));
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ // we are in an object, so figure
+ // out the property name and set an
+ // element in an associative array,
+ // for now
+ $parts = array();
+
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // "name":value pair
+ $key = $this->decode($parts[1]);
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // name:value pair, where name is unquoted
+ $key = $parts[1];
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ }
+
+ }
+
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+ // found a quote, and we are not inside a string
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+ //print("Found start of string at {$c}\n");
+
+ } elseif (($chrs{$c} == $top['delim']) &&
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
+ ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
+ // found a quote, we're in a string, and it's not escaped
+ // we know that it's not escaped becase there is _not_ an
+ // odd number of backslashes at the end of the string so far
+ array_pop($stk);
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '[') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-bracket, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+ //print("Found start of array at {$c}\n");
+
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+ // found a right-bracket, and we're in an array
+ array_pop($stk);
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '{') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-brace, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+ //print("Found start of object at {$c}\n");
+
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+ // found a right-brace, and we're in an object
+ array_pop($stk);
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($substr_chrs_c_2 == '/*') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a comment start, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+ $c++;
+ //print("Found start of comment at {$c}\n");
+
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+ // found a comment end, and we're in one now
+ array_pop($stk);
+ $c++;
+
+ for ($i = $top['where']; $i <= $c; ++$i)
+ $chrs = substr_replace($chrs, ' ', $i, 1);
+
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ }
+
+ }
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ return $obj;
+
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @todo Ultimately, this should just call PEAR::isError()
+ */
+ function isError($data, $code = null)
+ {
+ if (class_exists('pear')) {
+ return PEAR::isError($data, $code);
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+ is_subclass_of($data, 'services_json_error'))) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+if (class_exists('PEAR_Error')) {
+
+ class Services_JSON_Error extends PEAR_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+ }
+ }
+
+} else {
+
+ /**
+ * @todo Ultimately, this class shall be descended from PEAR_Error
+ */
+ class Services_JSON_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+
+ }
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+
+
+require_once 'PEAR/Exception.php';
+require_once 'MPD/Common.php';
+
+
+/**
+ * Central class for the Music Player Daemon objects
+ *
+ * Used to utilize the functionality of the provided classes
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+class Net_MPD
+{
+ /**
+ * The Net_MPD_Admin object
+ */
+ public $Admin;
+
+ /**
+ * The Net_MPD_Common object
+ */
+ public $Common;
+
+ /**
+ * The Net_MPD_Database object
+ */
+ public $Database;
+
+ /**
+ * The Net_MPD_Playback object
+ */
+ public $Playback;
+
+ /**
+ * The Net_MPD_Playlist object
+ */
+ public $Playlist;
+
+ /**
+ * Creates new instances of objects
+ * @param $host Host to connect to, optional (default: localhost)
+ * @param $port Port to connect to, optional (default: 6600)
+ * @param $pass Pass to connect to, optional (default: null)
+ * @return object or false on failure
+ */
+ function __construct($host = 'localhost', $port = 6600, $pass = null)
+ {
+ $this->Admin = self::factory('Admin' , $host, $port, $pass);
+ $this->Common = self::factory('Common' , $host, $port, $pass);
+ $this->Database = self::factory('Database', $host, $port, $pass);
+ $this->Playback = self::factory('Playback', $host, $port, $pass);
+ $this->Playlist = self::factory('Playlist', $host, $port, $pass);
+ }
+
+ /**
+ * Creates new instances of objects
+ * @param $class Class to initiate, with out Net_MPD_$class
+ * @param $host Host to connect to, optional (default: localhost)
+ * @param $port Port to connect to, optional (default: 6600)
+ * @param $pass Pass to connect to, optional (default: null)
+ * @return object or false on failure
+ */
+ public static function factory($class, $host = 'localhost', $port = 6600, $pass = null)
+ {
+ $class = ucfirst(strtolower($class));
+
+ if (!self::_loadClass($class)) {
+ return false;
+ }
+
+ $class = 'Net_MPD_' . $class;
+ $obj = new $class($host, $port, $pass);
+
+ return $obj;
+ }
+
+ /**
+ * Loads the class
+ * @param $class Class to include, with out Net_MPD_
+ * @return bool
+ */
+ protected static function _loadClass($class)
+ {
+ if (class_exists('Net_MPD_' . $class)) {
+ return true;
+ }
+ require_once 'Net/MPD/' . $class . '.php';
+ return true;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+
+/**
+ * API for the common peices of Music Player Daemon commands
+ *
+ * Used for basic interaction and output handeling, as well as
+ * several standard commands.
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+class Net_MPD_Common
+{
+ //Connection & Write Errors
+ const CONNECTION_NOT_OPENED = 100;
+ const CONNECTION_FAILED = 102;
+ const WRITE_FAILED = 103;
+
+ //MPD Errors
+ const ACK_NOT_LIST = 1;
+ const ACK_ARG = 2;
+ const ACK_PASSWORD = 3;
+ const ACK_PERMISSION = 4;
+ const ACK_UNKOWN = 5;
+ const ACK_NO_EXIST = 50;
+ const ACK_PLAYLIST_MAX = 51;
+ const ACK_SYSTEM = 52;
+ const ACK_PLAYLIST_LOAD = 53;
+ const ACK_UPDATE_ALREADY = 54;
+ const ACK_PLAYER_SYNC = 55;
+ const ACK_EXIST = 56;
+ const ACK_COMMAND_FAILED = -100;
+
+ //MPD Responces
+ const RESPONSE_OK = 'OK';
+
+ private $_connection = null;
+ protected $_errors = array();
+ private $_current_error = array();
+ protected $_commands = array();
+ protected $_output = array();
+ private $connection_params = array();
+
+
+
+ /**
+ * Set connection params
+ *
+ * @param $host host to connect to, (default: localhost)
+ * @param $port port to connec through, (default: 6600)
+ * @param $password password to send, (default: null)
+ * @return void
+ */
+ function __construct($host = 'localhost', $port = 6600, $password = null)
+ {
+ $this->connection_params['host'] = $host;
+ $this->connection_params['port'] = $port;
+ $this->connection_params['password'] = $password;
+ }
+
+
+
+ /**
+ * Connect to MPD
+ *
+ * @return bool
+ */
+ public function connect()
+ {
+ if ($this->isConnected()) {
+ return true;
+ }
+ $connection = @fsockopen($this->connection_params['host'], $this->connection_params['port'], $errn, $errs, 4);
+ if ($connection) {
+ $this->_connection = $connection;
+ // Read from the source until its ready for commands
+ //$this->read();
+ while (!feof($this->_connection)) {
+ $line = fgets($this->_connection);
+ if (trim(substr($line, 0, 2)) == self::RESPONSE_OK) {
+ break;
+ }
+ }
+ if (!is_null($this->connection_params['password'])) {
+ if (!$this->runCommand('password', $this->connection_params['password'])) {
+ throw new PEAR_Exception('Password invalid.', self::ACK_PASSWORD);
+ }
+ }
+ return true;
+ }
+ throw new PEAR_Exception('Error connecting: '.$errn.' ; '.$errs, self::CONNECTION_FAILED);
+ }
+
+
+
+ /**
+ * Check connection status
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ if (!is_resource($this->_connection)) {
+ return false;
+ }
+ return true;
+ }
+
+
+
+ /**
+ * Disconnect from MPD
+ *
+ * @return bool
+ */
+ public function disconnect()
+ {
+ $this->runCommand('close');
+ fclose($this->_connection);
+ $this->_connection = null;
+ return true;
+ }
+
+
+
+ /**
+ * Write data to the socket
+ *
+ * @param $command string data to be sent
+ *
+ * @return bool
+ *
+ */
+ function write($data)
+ {
+ //Are we connected?
+ if (!$this->isConnected()) {
+ // Try to connect
+ $this->connect();
+ }
+ //Write the data
+ if (!fwrite($this->_connection, $data."\r\n")) {
+ throw new PEAR_Exception('Write failed', self::WRITE_FAILED);
+ }
+ $this->_commands[] = $data;
+ return true;
+ }
+
+
+
+ /**
+ * Read data from the socket
+ *
+ * @return array of raw output
+ *
+ */
+ function read()
+ {
+ //Are we connected?
+ if (!$this->isConnected()) {
+ throw new PEAR_Exception('Not connected', self::CONNECTION_NOT_OPENED);
+ }
+ //Loop through the connection, putting the data into $line
+ $output = array();
+ while (!feof($this->_connection)) {
+ $line = fgets($this->_connection);
+ if (preg_match('/^ACK \[(.*?)\@(.*?)\] \{(.*?)\} (.*?)$/', $line, $matches)) {
+ //If the output is an ACK error
+ //$this->runCommand('clearerror'); //Cleanup the error
+ $this->_errors[] = $matches;
+ $this->_current_error = array('ack' => $matches[1], 'func' => $matches[3], 'error' => $matches[4]);
+ throw new PEAR_Exception('Command Failed', self::ACK_COMMAND_FAILED);
+ } elseif (trim($line) == self::RESPONSE_OK) {
+ //The last line of output was hit, close the loop
+ break;
+ } else {
+ //Output from the server added to the return array
+ $output[] = $line;
+ }
+ }
+ return $output;
+ }
+
+
+
+ /**
+ * Get the current error data
+ *
+ * @return array of error data
+ */
+ public function getErrorData()
+ {
+ return $this->_current_error;
+ }
+
+
+ public function clearError() {
+ $this->runCommand('clearerror'); //Cleanup the error
+ }
+
+ /**
+ * Run command
+ *
+ * @param $command string a command to be executed through MPD
+ * @param $args mixed string for a single argument, array for multiple
+ * @param $parse mixed false to parse the output, int for parse style
+ *
+ * @return array of server output
+ */
+ public function runCommand($command, $args = array(), $parse = 0)
+ {
+ //Generate the command
+ if (is_array($args)) {
+ foreach($args as $arg) {
+ $command.= ' "'.str_replace('"', '\"', $arg) .'"';
+ }
+ } elseif (!is_null($args)) {
+ $command.= ' "'.str_replace('"', '\"', $args) .'"';
+ }
+ //Write and then capture the output
+ $this->write($command);
+ $output = $this->read();
+
+ $this->_output[] = array($command, $output);
+ if ($output === array()) {
+ return true;
+ }
+ if ($parse !== false) {
+ return $this->parseOutput($output, $parse);
+ }
+ return $output;
+ }
+
+
+
+ /**
+ * Parse MPD output on a line-by-line basis
+ * creating output that is easy to work with
+ *
+ * @param $input array of input from MPD
+ * @param $style int style number,'0' for the "intelligent" sorting
+ *
+ * @return array
+ */
+ public function parseOutput($input, $style = 0)
+ {
+ if (!is_array($input)) {
+ $input = array($input);
+ }
+ $count = array('outputs' => 0, 'file' => -1, 'key' => 0);
+ $used_keys = array();
+ $output = array();
+ $prev = array('key' => null, 'value' => null);
+ $dirtoggle = false;
+ foreach($input as $line) {
+ if (is_array($line)) {
+ $this->_errors[] = 'Server output not expected: '.print_r($line, true);
+ continue;
+ } else {
+ $parts = explode(': ', $line, 2);
+ if (!isset($parts[0], $parts[1])) {
+ $this->errors[] = 'Server output not expected: '.$line;
+ continue;
+ }
+ }
+ $key = trim($parts[0]);
+ $value = trim($parts[1]);
+ if ($style == 0) {
+ switch ($key) {
+ //The following has to do strictly
+ //with files in the output
+ case 'file':
+ case 'Artist':
+ case 'Album':
+ case 'Title':
+ case 'Track':
+ case 'Name':
+ case 'Genre':
+ case 'Date':
+ case 'Composer':
+ case 'Performer':
+ case 'Comment':
+ case 'Disc':
+ case 'Id':
+ case 'Pos':
+ case 'Time':
+ case 'cpos':
+ if ($key == 'file'||$key== 'cpos') {
+ $count['file']++;
+ }
+ $output['file'][$count['file']][$key] = $value;
+ break;
+
+ //The next section is for a 'stats' call
+ case 'artists':
+ case 'albums':
+ case 'songs':
+ case 'uptime':
+ case 'playtime':
+ case 'db_playtime':
+ case 'db_update':
+ $output['stats'][$key] = $value;
+ break;
+
+ //Now for a status call:
+ case 'volume':
+ case 'repeat':
+ case 'random':
+ case 'playlistlength':
+ case 'xfade':
+ case 'state':
+ case 'song':
+ case 'songid':
+ case 'time':
+ case 'bitrate':
+ case 'audio':
+ case 'updating_db':
+ case 'error':
+ $output['status'][$key] = $value;
+ break;
+
+ //Outputs
+ case 'outputid':
+ case 'outputname':
+ case 'outputenabled':
+ if ($key == 'outputid') {
+ $count['outputs']++;
+ }
+ $output['outputs'][$count['outputs']][$key] = $value;
+ break;
+
+ //The 'playlist' case works in 2 scenarios
+ //1) in a file/directory listing
+ //2) in a status call
+ // This is to determine if it was in a status call
+ // or in a directory call.
+ case 'playlist':
+ if ($prev['key'] == 'random') {
+ $output['status'][$key] = $value;
+ } else {
+ $output[$key][] = $value;
+ }
+ break;
+
+ //Now that we've covered most of the weird
+ //options of output,
+ //lets cover everything else!
+ default:
+ if (isset($used_keys[$key])) {
+ $used_keys = array();
+ $count['key']++;
+ }
+ $used_keys[$key] = true;
+ //$output[$count['key']][$key] = $value;//This is rarely useful
+ $output[$key][] = $value;
+ break;
+ }
+ } elseif ($style == 1) {
+ $output[$key][] = $value;
+ }
+ if ($key == 'directory') {
+ $dirtoggle = true;
+ }
+ $prev['key'] = $key;
+ $prev['value'] = $value;
+ }
+ return $output;
+ }
+
+
+
+ /**
+ * A method to access errors
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->_errors;
+ }
+
+
+
+ /**
+ * Used to discover commands that are not available
+ *
+ * @return array (null on no functions not being available)
+ */
+ public function getNotCommands()
+ {
+ $cmds = $this->runCommand('notcommands');
+ if (!isset($cmds['command'])) {
+ return array();
+ }
+ return $cmds['command'];
+ }
+
+
+
+ /**
+ * Used to discover which commands are available
+ *
+ * @return array (null on no functions being available
+ */
+ public function getCommands()
+ {
+ $cmds = $this->runCommand('commands');
+ if (!isset($cmds['command'])) {
+ return array();
+ }
+ return $cmds['command'];
+ }
+
+ public function hasCommand($name) {
+ $cmds = $this->getCommands();
+ foreach ($cmds as $cmd)
+ if($cmd==$name)
+ return true;
+ return false;
+ }
+
+
+
+ /**
+ * Ping the MPD server to keep the connection running
+ *
+ * @return bool
+ */
+ public function ping()
+ {
+ if ($this->runCommand('ping')) {
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Get various statistics about the MPD server
+ *
+ * @return array
+ */
+ public function getStats()
+ {
+ $stats = $this->runCommand('stats');
+ if (!isset($stats['stats'])) {
+ return false;
+ }
+ return $stats['stats'];
+ }
+
+
+
+ /**
+ * Get the status of the MPD server
+ *
+ * @return array
+ */
+ public function getStatus()
+ {
+ $status = $this->runCommand('status');
+ if (!isset($status['status'])) {
+ return false;
+ }
+ return $status['status'];
+ }
+}
+?>
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ *
+ *
+ * API for the administrative portion of Music Player Daemon commands
+ *
+ * Used for maintaining and controlling various administrative tasks
+ * of the MPD software.
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+class Net_MPD_Admin extends Net_MPD_Common
+{
+
+ /**
+ * List available audio outputs
+ *
+ * @return array or int on failure
+ */
+ public function getOutputs()
+ {
+ return $this->runCommand('outputs');
+ }
+
+ /**
+ * Disables an audio output
+ *
+ * @param $id int output Id to disable
+ * @return bool
+ */
+ public function disableOutput($id)
+ {
+ return $this->runCommand('disableoutput', $id);
+ }
+
+ /**
+ * Enables an audio output
+ *
+ * @param $id int Id to enable
+ * @return bool
+ */
+ public function enableOutput($id)
+ {
+ return $this->runCommand('enableoutput', $id);
+ }
+
+ /**
+ * Kills the MPD server in a safe way, saving state if possible
+ *
+ * @return bool
+ */
+ public function kill()
+ {
+ $r = $this->runCommand('kill');
+ if ($r) {
+ @$this->disconnect();
+ }
+ return true;
+ }
+
+ /**
+ * Updates the music database
+ *
+ * @param $path string path which to search for music, optional
+ * @return bool
+ */
+ public function updateDatabase($path = '')
+ {
+ $this->runCommand('update', $path);
+ return true;
+ }
+}
+?>
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+
+/**
+ * API for the database portion of Music Player Daemon commands
+ *
+ * Used for maintaining and working with the MPD database
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+class Net_MPD_Database extends Net_MPD_Common
+{
+ /**
+ * Case sensitive search for data in the database
+ *
+ * @param array $params array('search_field' => 'search for')
+ * @param bool $caseSensitive True for case sensitivity, false for not [default false]
+ * @return array
+ */
+ public function find($params, $caseSensitive = false)
+ {
+ $prms = array();
+ foreach($params as $key => $value) {
+ $prms[] = $key;
+ $prms[] = $value;
+ }
+ $cmd = $caseSensitive ? 'find' : 'search';
+
+ $out = $this->runCommand($cmd, $prms);
+ if (!isset($out['file'])) {
+ return array();
+ }
+ return $out['file'];
+ }
+
+
+
+ /**
+ * List all metadata of matches to the search
+ *
+ * @param string $metadata1 metadata to list
+ * @param string $metadata2 metadata field to search in, optional
+ * @param string $search data to search for in search field,
+ * required if search field provided
+ * @return array
+ */
+ public function getMetadata($metadata1, $metadata2 = null, $search = null)
+ {
+ //Make sure that if metadata2 is set, search is as well
+ if (!is_null($metadata2)) {
+ if (is_null($search)) {
+ return false;
+ }
+ }
+ if (!is_null($metadata2)) {
+ $out = $this->runCommand('list', array($metadata1, $metadata2, $search), 1);
+ } else {
+ $out = $this->runCommand('list', $metadata1, 1);
+ }
+ return $out[$metadata1];
+ }
+
+
+
+ /**
+ * Lists all files and folders in the directory recursively
+ *
+ * @param $dir string directory to start in, optional
+ * @return array
+ */
+ public function getAll($dir = '')
+ {
+ return $this->runCommand('listall', $dir, 1);
+ }
+
+
+
+ /**
+ * Lists all files/folders recursivly, listing any related informaiton
+ *
+ * @param $dir string directory to start in, optional
+ * @return array
+ */
+ public function getAllInfo($dir = '')
+ {
+ return $this->runCommand('listallinfo', $dir);
+ }
+
+ /**
+ * Lists content of the directory
+ *
+ * @param $dir string directory to work in, optional
+ * @return array
+ */
+ public function getInfo($dir = '')
+ {
+ return $this->runCommand('lsinfo', $dir);
+ }
+}
+?>
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+/**
+ * API for the playback portion of Music Player Daemon commands
+ *
+ * For controlling playback aspects of MPD
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @version CVS: $ID:$
+ */
+class Net_MPD_Playback extends Net_MPD_Common
+{
+ /**
+ * Gets the current song and related information
+ *
+ * @return array of data
+ */
+ public function getCurrentSong()
+ {
+ $out = $this->runCommand('currentsong');
+ if (!isset($out['file'][0])) {
+ return false;
+ }
+ return $out['file'][0];
+ }
+
+
+
+ /**
+ * Set crossfade between songs
+ *
+ * @param $sec int, seconds to crossfade
+ * @return bool
+ */
+ public function setCrossfade($sec)
+ {
+ $this->runCommand('crossfade', $sec);
+ return true;
+ }
+
+
+
+ /**
+ * Continue to the next song
+ *
+ * @return bool
+ */
+ public function nextSong()
+ {
+ $this->runCommand('next');
+ return true;
+ }
+
+ /**
+ * Go back to the previous song
+ *
+ * @return bool
+ */
+ public function previousSong()
+ {
+ $this->runCommand('previous');
+ return true;
+ }
+
+
+
+ /**
+ * Pauses or plays the audio
+ *
+ * @return bool
+ */
+ public function pause()
+ {
+ $this->runCommand('pause');
+ return true;
+ }
+
+
+
+ /**
+ * Starts playback
+ *
+ * @param $song int, song position in playlist to start playing at
+ * @return bool
+ */
+ public function play($song = 0)
+ {
+ $this->runCommand('play', $song);
+ return true;
+ }
+
+ /**
+ * Starts playback by Id
+ *
+ * @param $song int, song Id
+ * @return bool
+ */
+ public function playId($song = 0)
+ {
+ $this->runCommand('playid', $song);
+ return true;
+ }
+
+
+
+ /**
+ * Sets 'random' mode on/off
+ *
+ * @param $on bool true or false, for random or not (respectively),
+ optional
+ * @return bool
+ */
+ public function random($on = false)
+ {
+ $this->runCommand('random', (int)$on);
+ return true;
+ }
+
+
+
+ /**
+ * Sets 'random' mode on/off
+ * @access public
+ * @param $on bool true or false, for repeat or not (respectively),
+ optional
+ * @return true
+ */
+ public function repeat($on = false)
+ {
+ $this->runCommand('repeat', (int)$on);
+ return true;
+ }
+
+
+
+ /**
+ * Seek a position in a song
+ *
+ * @param $song int song position in playlist
+ * @param $time int time in seconds to seek to
+ * @return bool
+ */
+ public function seek($song, $time)
+ {
+ $this->runCommand('seek', array($song, $time));
+ return true;
+ }
+
+
+
+ /**
+ * Seek a position in a song
+ *
+ * @param $song int song Id
+ * @param $time int time in seconds to seek to
+ * @return bool
+ */
+ public function seekId($song, $time)
+ {
+ $this->runCommand('seekid', array($song, $time));
+ return true;
+ }
+
+
+
+ /**
+ * Set volume
+ *
+ * @param $vol int volume
+ * @return true
+ */
+ public function setVolume($vol)
+ {
+ $this->runCommand('setvol', $vol);
+ return true;
+ }
+
+
+
+ /**
+ * Stop playback
+ *
+ * @return bool
+ */
+ public function stop()
+ {
+ $this->runCommand('stop');
+ return true;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * MPD Interaction API
+ *
+ * Net_MPD deals with socket interaction with MPD to ease the
+ * use of MPD in web applications.
+ *
+ * PHP Version 5
+ *
+ * @package Net_MPD
+ * @category Networking
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license PHP License 3.01
+ * @version CVS: $ID:$
+ */
+
+/**
+ * API for the playlist portion of Music Player Daemon commands
+ *
+ * Used for maintaining, creating, and utilizing playlists in MPD
+ *
+ * @category Networking
+ * @package Net_MPD
+ * @author Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license http://www.php.net/license/3_01.txt
+ * @vers
+ */
+class Net_MPD_Playlist extends Net_MPD_Common
+{
+ /**
+ * List playlists in specified directory
+ *
+ * @param $dir string directory path, optional
+ * @return bool true on success int on failure
+ */
+ public function getPlaylists($dir = '')
+ {
+ $out = $this->runCommand('lsinfo', $dir);
+ return $out['playlist'];
+ }
+
+ /**
+ * Search for data in the playlist
+ *
+ * @param array $params array('search_field' => 'search for')
+ * @param bool $caseSensitive True for case sensitivity, false for not [default false]
+ * @return array
+ */
+ public function find($params, $caseSensitive = false)
+ {
+ $prms = array();
+ foreach($params as $key => $value) {
+ $prms[] = $key;
+ $prms[] = $value;
+ }
+ $cmd = $caseSensitive ? 'playlistfind' : 'playlistsearch';
+
+ $out = $this->runCommand($cmd, $prms);
+ if (!isset($out['file'])) {
+ return array();
+ }
+ return $out['file'];
+ }
+
+ /* Tests whether playlistfind is avilable. If playlistfind
+ * is available playlistsearch is as well
+ *
+ * @return boolean
+ */
+ public function hasFind() {
+ return $this->hasCommand("playlistfind");
+ }
+
+ /**
+ * Add file to playlist
+ *
+ * @param $file string filename
+ * @return bool
+ */
+ public function addSong($file)
+ {
+ $this->runCommand('add', $file);
+ return true;
+ }
+
+
+
+ /**
+ * Clear the playlist
+ *
+ * @return bool
+ */
+ public function clear()
+ {
+ $this->runCommand('clear');
+ return true;
+ }
+
+
+
+ /**
+ * Gets the current song and related information
+ *
+ * @return array of data
+ */
+ public function getCurrentSong()
+ {
+ $out = $this->runCommand('currentsong');
+ if (!isset($out['file'][0])) {
+ return false;
+ }
+ return $out['file'][0];
+ }
+
+
+
+ /**
+ * Delete song from playlist
+ *
+ * @param $song int song position in playlist
+ * @return bool
+ */
+ public function deleteSong($song)
+ {
+ $this->runCommand('delete', $song);
+ return true;
+ }
+
+
+
+ /**
+ * Delete song from playlist by song Id
+ *
+ * @param $id int song Id
+ * @return bool
+ */
+ public function deleteSongId($id)
+ {
+ $this->runCommand('deleteid', $id);
+ return true;
+ }
+
+
+
+ /**
+ * Loads a playlist into the current playlist
+ *
+ * @param $playlist string playlist name
+ * @return bool
+ */
+ public function loadPlaylist($playlist)
+ {
+ $this->runCommand('load', $playlist);
+ return true;
+ }
+
+
+
+ /**
+ * Move song in the playlist
+ *
+ * @param $from int song position in the playlist
+ * @param $to int song position to move it to
+ * @return bool
+ */
+ public function moveSong($from, $to)
+ {
+ $this->runCommand('move', array($from, $to));
+ return true;
+ }
+
+
+
+ /**
+ * Move song in the playlist by Id
+ *
+ * @param $from int song Id
+ * @param $to int song Id to move it to
+ * @return bool
+ */
+ public function moveSongId($fromId, $toId)
+ {
+ $this->runCommand('moveid', array($fromId, $toId));
+ return true;
+ }
+
+
+
+ /**
+ * Displays metadata for songs in the playlist by position Id
+ *
+ * @param $song int song position, optional
+ * @return array of song metadata
+ */
+ public function getPlaylistInfo($song = null)
+ {
+ $out = $this->runCommand('playlistinfo', $song, 0);
+ return isset($out['file']) ? $out['file'] : array();
+ }
+
+
+
+ /**
+ * Displays metadata for songs in the playlist
+ *
+ * @param $song int song Id, optional
+ * @return array of song metadata
+ */
+ public function getPlaylistInfoId($song = null)
+ {
+ return $this->runCommand('playlistid', $song);
+ }
+
+
+
+ /**
+ * Get playlist changes
+ *
+ * @param $version int playlist version
+ * @param $limit boolean true to limit return
+ * to only position and id
+ *
+ * @return array of changes
+ */
+ public function getChanges($version, $limit = false)
+ {
+ $cmd = $limit ? 'plchangesposid' : 'plchanges';
+
+ return $this->runCommand($cmd, $version);
+ }
+
+
+
+ /**
+ * Delete a playlist
+ *
+ * @param $playlist string playlist name
+ * @return true
+ */
+ public function deletePlaylist($playlist)
+ {
+ $this->runCommand('rm', $playlist);
+ return true;
+ }
+
+
+
+ /**
+ * Save the playlist
+ *
+ * @param $playlist string playlist name
+ * @return bool
+ */
+ public function savePlaylist($playlist)
+ {
+ $this->runCommand('save', $playlist);
+ return true;
+ }
+
+
+
+ /**
+ * Shuffle the playlist
+ *
+ * @return true
+ */
+ public function shuffle()
+ {
+ $this->runCommand('shuffle');
+ return true;
+ }
+
+
+
+ /**
+ * Swap song by position in the playlist
+ *
+ * @param $song1 int song position from
+ * @param $song2 int song position to
+ * @return bool
+ */
+ public function swapSong($song1, $song2)
+ {
+ $this->runCommand('swap', array($song1, $song2));
+ return true;
+ }
+
+
+
+ /**
+ * Swaps a song with another song, by Id
+ *
+ * @param $song1 int Id of the first song
+ * @param $song2 int Id of the second song
+ * @return true
+ */
+ public function swapSongId($songId1, $songId2)
+ {
+ $this->runCommand('swapid', array($songId1, $songId2));
+ return true;
+ }
+}
+?>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE package SYSTEM "http://pear.php.net/dtd/package-1.0">
+<package version="1.0" packagerversion="1.4.11">
+ <name>Net_MPD</name>
+ <summary>Music Player Daemon interaction API</summary>
+ <description>Rrovides wrappers to easily use the MPD socket commands.
+ </description>
+ <maintainers>
+ <maintainer>
+ <user>itrebal</user>
+ <name>Graham Christensen</name>
+ <email>graham.christensen@itrebal.com</email>
+ <role>lead</role>
+ </maintainer>
+ </maintainers>
+ <release>
+ <version>1.0.0</version>
+ <date>2007-01-18</date>
+ <license>PHP License 3.01</license>
+ <state>stable</state>
+ <notes>Removed unecissary try/catch blocks. Moving to stable.
+ </notes>
+ <deps>
+ <dep type="php" rel="ge" version="5.1.4"/>
+ </deps>
+ <provides type="class" name="Net_MPD" />
+ <provides type="function" name="Net_MPD::factory" />
+ <provides type="class" name="Net_MPD_Admin" extends="Net_MPD_Common" />
+ <provides type="function" name="Net_MPD_Admin::getOutputs" />
+ <provides type="function" name="Net_MPD_Admin::disableOutput" />
+ <provides type="function" name="Net_MPD_Admin::enableOutput" />
+ <provides type="function" name="Net_MPD_Admin::kill" />
+ <provides type="function" name="Net_MPD_Admin::updateDatabase" />
+ <provides type="class" name="Net_MPD_Common" />
+ <provides type="function" name="Net_MPD_Common::connect" />
+ <provides type="function" name="Net_MPD_Common::isConnected" />
+ <provides type="function" name="Net_MPD_Common::disconnect" />
+ <provides type="function" name="Net_MPD_Common::write" />
+ <provides type="function" name="Net_MPD_Common::read" />
+ <provides type="function" name="Net_MPD_Common::getErrorData" />
+ <provides type="function" name="Net_MPD_Common::runCommand" />
+ <provides type="function" name="Net_MPD_Common::parseOutput" />
+ <provides type="function" name="Net_MPD_Common::getErrors" />
+ <provides type="function" name="Net_MPD_Common::getNotCommands" />
+ <provides type="function" name="Net_MPD_Common::getCommands" />
+ <provides type="function" name="Net_MPD_Common::ping" />
+ <provides type="function" name="Net_MPD_Common::getStats" />
+ <provides type="function" name="Net_MPD_Common::getStatus" />
+ <provides type="class" name="Net_MPD_Database" extends="Net_MPD_Common" />
+ <provides type="function" name="Net_MPD_Database::find" />
+ <provides type="function" name="Net_MPD_Database::getMetadata" />
+ <provides type="function" name="Net_MPD_Database::getAll" />
+ <provides type="function" name="Net_MPD_Database::getAllInfo" />
+ <provides type="function" name="Net_MPD_Database::getInfo" />
+ <provides type="class" name="Net_MPD_Playback" extends="Net_MPD_Common" />
+ <provides type="function" name="Net_MPD_Playback::getCurrentSong" />
+ <provides type="function" name="Net_MPD_Playback::setCrossfade" />
+ <provides type="function" name="Net_MPD_Playback::nextSong" />
+ <provides type="function" name="Net_MPD_Playback::previousSong" />
+ <provides type="function" name="Net_MPD_Playback::pause" />
+ <provides type="function" name="Net_MPD_Playback::play" />
+ <provides type="function" name="Net_MPD_Playback::playId" />
+ <provides type="function" name="Net_MPD_Playback::random" />
+ <provides type="function" name="Net_MPD_Playback::repeat" />
+ <provides type="function" name="Net_MPD_Playback::seek" />
+ <provides type="function" name="Net_MPD_Playback::seekId" />
+ <provides type="function" name="Net_MPD_Playback::setVolume" />
+ <provides type="function" name="Net_MPD_Playback::stop" />
+ <provides type="class" name="Net_MPD_Playlist" extends="Net_MPD_Common" />
+ <provides type="function" name="Net_MPD_Playlist::getPlaylists" />
+ <provides type="function" name="Net_MPD_Playlist::addSong" />
+ <provides type="function" name="Net_MPD_Playlist::clear" />
+ <provides type="function" name="Net_MPD_Playlist::getCurrentSong" />
+ <provides type="function" name="Net_MPD_Playlist::deleteSong" />
+ <provides type="function" name="Net_MPD_Playlist::deleteSongId" />
+ <provides type="function" name="Net_MPD_Playlist::loadPlaylist" />
+ <provides type="function" name="Net_MPD_Playlist::moveSong" />
+ <provides type="function" name="Net_MPD_Playlist::moveSongId" />
+ <provides type="function" name="Net_MPD_Playlist::getPlaylistInfo" />
+ <provides type="function" name="Net_MPD_Playlist::getPlaylistInfoId" />
+ <provides type="function" name="Net_MPD_Playlist::getChanges" />
+ <provides type="function" name="Net_MPD_Playlist::deletePlaylist" />
+ <provides type="function" name="Net_MPD_Playlist::savePlaylist" />
+ <provides type="function" name="Net_MPD_Playlist::shuffle" />
+ <provides type="function" name="Net_MPD_Playlist::swapSong" />
+ <provides type="function" name="Net_MPD_Playlist::swapSongId" />
+ <filelist>
+ <file role="php" baseinstalldir="/" md5sum="f4adbf28dd06b747929ddd7345040571" name="Net/MPD.php"/>
+ <file role="php" baseinstalldir="/" md5sum="b2a9a9768c50a610d4ba7055dfef0e46" name="Net/MPD/Admin.php"/>
+ <file role="php" baseinstalldir="/" md5sum="a2301a1e04df755d70965ffe5265677f" name="Net/MPD/Common.php"/>
+ <file role="php" baseinstalldir="/" md5sum="b76d6fbf71fcede04d8816e1354a8e42" name="Net/MPD/Database.php"/>
+ <file role="php" baseinstalldir="/" md5sum="8a8a565029f2c21fc0683cbe3139ec46" name="Net/MPD/Playback.php"/>
+ <file role="php" baseinstalldir="/" md5sum="18d4bca6d89d276769ecee5186be4235" name="Net/MPD/Playlist.php"/>
+ <file role="doc" baseinstalldir="/" md5sum="3d2f2dd2aea4f3abebc9c80f11b4a097" name="Net/MPD/docs/listArtists.php"/>
+ <file role="doc" baseinstalldir="/" md5sum="a69ec1ca3c692b53e9d643777d6ef0ee" name="Net/MPD/docs/listCurrentPlaylist.php"/>
+ <file role="doc" baseinstalldir="/" md5sum="5c7b6ab616a1fcfbb98a000d9bfb57c6" name="Net/MPD/docs/listSongs.php"/>
+ </filelist>
+ </release>
+ <changelog>
+ <release>
+ <version>0.1.0dev1</version>
+ <date>2006-11-07</date>
+ <state>devel</state>
+ <notes>Initial release.
+ </notes>
+ </release>
+ <release>
+ <version>0.2.0dev</version>
+ <date>2006-11-12</date>
+ <state>devel</state>
+ <notes>- Copied `getCurrentSong' from Net_MPD_Playlist to Net_MPD_Playback.
+- Changed MPD.php to provied an object uniting the sub-objects. $mpd->Playlist->addSong() for example. Left the factory for other uses.
+- Changed MPD/Common.php to auto-connect when a command is run. This is to prevent several connections being opend without need, if the Net_MPD class is used to group them all. Also simplifies the connection process.
+- Moved the connection parameters back into the __construct method of the Net_MPD_Common class.
+- Bugfix: defined $output before adding to it as an array, in Net_MPD_Common->read() function.
+ </notes>
+ </release>
+ <release>
+ <version>0.3.0dev</version>
+ <date>2006-12-10</date>
+ <state>devel</state>
+ <notes>- Fixed bug #9392; Error codes can now be accessed using Exception::getCode()
+- Fixed bug #9334; Now installs into PEAR/Net/
+- Fixed bug #9341; Now permits $e->getCode();
+ </notes>
+ </release>
+ <release>
+ <version>1.0.0</version>
+ <date>2007-01-18</date>
+ <state>stable</state>
+ <notes>Removed unecissary try/catch blocks. Moving to stable.
+ </notes>
+ </release>
+ </changelog>
+</package>
--- /dev/null
+<?php
+if(function_exists("mb_internal_encoding"))
+ mb_internal_encoding("UTF-8");
+define('HASH_PASS', "********");
+define('SALT_LENGTH', 15);
+
+session_start();
+
+/* make sure . is first in include path */
+set_include_path("." . PATH_SEPARATOR . get_include_path());
+require_once('Net/MPD.php');
+
+$release_version = "0.5.5";
+$release_date = "18-01-2008";
+
+$config_dir = "../config";
+
+/* should be metadata_dir, no time to change now TODO */
+$cover_dir = "$config_dir/metadata/";
+$metadata_dir = $cover_dir;
+
+
+$theme_dir = "../theme/";
+
+$config = @simplexml_load_file("../config/config.xml");
+$error_msg = false;
+
+/* playlist fields */
+$pl_fields = array("Title", "Album", "Artist", "Track", "Name", "Genre", "Date", "Composer", "Performer", "Comment", "Disc");
+
+if(!$config&&!isset($_GET['new_config'])) {
+ header("Location: config.php?new_config=true") or die("Unable to redirect, go here <a href='config.php?new_config=true'>config</a>");
+ exit();
+}
+
+$language = get_config("lang", "en");
+
+
+$selected_theme = get_config("theme", "default");
+if(!is_theme_dir_ok($theme_dir . $selected_theme))
+ $selected_theme = "default";
+
+$lpass = get_config('login_pass');
+
+if(!is_null($lpass)&&$lpass!="") {
+ if(!isset($_SESSION['logged_in'])||!$_SESSION['logged_in']) {
+ if(!isset($no_require_login)) {
+ header("Location: login.php");
+ echo "Wrong password";
+ exit();
+ }
+ }
+}
+
+function get_config($name, $default = null) {
+ global $config;
+ if(isset($config->$name)) {
+ if(trim($config->$name)=="")
+ return $default;
+ return ((string)$config->$name);
+ }
+ else {
+ return $default;
+ }
+}
+
+function get_selected_plfields() {
+ global $config, $pl_fields;
+ $plentry = false;
+ if(isset($config->plentry))
+ $plentry = $config->plentry;
+ $ret = array();
+ $i = 0;
+ foreach($pl_fields as $field) {
+ if($plentry) {
+ $ret[] = ((string)$plentry->$field)!="";
+ }
+ else {
+ $ret[] = $i++<3;
+ }
+ }
+ return $ret;
+}
+
+function set_config($name, $value) {
+
+}
+/* if a key is found to be numeric it will be replaced by numb_replace
+*/
+function array_to_xml($arr, $xml = null, $numb_replace = "elem") {
+ if(is_null($xml)) {
+ $xml = simplexml_load_string("<?xml version='1.0' ?><root/>");
+ }
+ foreach($arr as $key => $value) {
+ if(is_numeric($key))
+ $key = $numb_replace;
+ if(is_array($value)) {
+ $tmp = $xml->addChild($key);
+ array_to_xml($value, $tmp, $numb_replace);
+ }
+ else {
+ $xml->addChild($key, htmlspecialchars($value));
+ }
+ }
+ return $xml;
+}
+
+function get_mpd($type) {
+ try {
+ $port = 6600;
+ if(is_numeric(get_config("mpd_port")))
+ $port = (int) get_config("mpd_port");
+ $ret = Net_MPD::factory($type, get_config('mpd_host'), intval($port), get_config("mpd_pass"));
+ $ret->connect();
+ return $ret;
+ }
+ catch(PEAR_Exception $e) {
+ return false;
+ }
+}
+function get_playlist() {
+ return get_mpd("Playlist");
+}
+function get_playback() {
+ return get_mpd("Playback");
+}
+function get_database() {
+ return get_mpd("Database");
+}
+function get_admin() {
+ return get_mpd("Admin");
+}
+
+/* mimic behaviour of java System.currentTimeMillis() */
+// ex: 1172151695935.8
+function current_time_millis() {
+ return microtime(true)*1000;
+}
+
+// returns array with available themes
+function get_available_themes() {
+ global $theme_dir;
+ $dirs = scandir($theme_dir);
+ $themes = array();
+ foreach($dirs as $dir) {
+ if($dir=="."||$dir=="..")
+ continue;
+
+ if(is_theme_dir_ok($theme_dir . $dir)) {
+ $themes[] = $dir;
+ }
+ }
+ return $themes;
+}
+
+function is_theme_dir_ok($tdir) {
+ return is_dir($tdir) && file_exists($tdir . "/theme.css") && file_exists($tdir . "/theme.js");
+}
+
+function generate_hash($val, $salt = false) {
+ if(function_exists("hash")) {
+ if($salt===false)
+ $salt = substr(md5(uniqid(rand(), true)), 0, SALT_LENGTH);
+
+ $p = hash("sha256", $val . $salt);
+ return "sha:" . $p . $salt;
+ }
+ else return $val;
+}
+function check_hash($proper, $check) {
+ $len = strlen($proper);
+ $nhash = generate_hash($check, substr($proper, $len-SALT_LENGTH));
+ if($proper==$nhash) {
+ return true;
+ }
+ return false;
+}
+
+/* someone find me a php equiv */
+function str_byte_count($data) {
+ if(function_exists("mb_strlen")) {
+ /* count bytes, not characters if utf-8,
+ * I imagine this works, but hard to actually test */
+ return mb_strlen($data, "ascii");
+ }
+ else {
+ return strlen($data);
+ }
+}
+?>
--- /dev/null
+<?php
+/*
+ Pitchfork Music Player Daemon Client
+ Copyright (C) 2007 Roger Bystrøm
+
+ 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; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+ /* test for required stuff */
+ $fn_prob = false;
+
+ foreach(array("simplexml_load_string", "simplexml_load_file") as $fn) {
+ if(!function_exists($fn)) {
+ $fn_prob = $fn_prob?"$fn_prob $fn":"$fn";
+ }
+ }
+
+ if($fn_prob) {
+ echo "You are missing function(s): $fn_prob. Cowardly bailing out..\n";
+ echo "This means that your php installation is either compiled without simplexml support or you have an old version of PHP. Version 5.1.3 or later is required.\n";
+ exit();
+ }
+
+?>
--- /dev/null
+<?php
+/*
+ Pitchfork Music Player Daemon Client
+ Copyright (C) 2007 Roger Bystrøm
+
+ 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; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+ header("Location: player/index.php");
+ exit();
+?>
--- /dev/null
+<project name="jorbis-pitchfork" basedir="." default="all">
+
+ <!--
+ run "ant jar" to create the needed jar
+ To sign it you'll need a selfsigned certificate, see seperate
+ comment about that
+ -->
+ <property name="source" value="src"/>
+ <property name="build" value="build"/>
+ <property name="classes" value="${build}/classes"/>
+ <property name="jarfile" value="jorbis-pitchfork.jar"/>
+
+ <target name="clean">
+ <delete dir="${build}"/>
+ </target>
+
+ <target name="compile">
+ <mkdir dir="${classes}"/>
+ <javac srcdir="${source}" destdir="${classes}"/>
+ </target>
+
+ <target name="jar" depends="compile">
+
+ <jar destfile="jorbis-pitchfork.jar" basedir="${classes}">
+ <manifest>
+ <attribute name="Main-Class" value="JorbisPlayer"/>
+ </manifest>
+ </jar>
+ </target>
+
+
+ <!-- To create your own certificate:
+ keytool -genkey -alias pitchfork -validity 3650
+ keytool -selfcert -alias pitchfork -validity 3650
+ -->
+ <target name="signjar" depends="jar">
+ <signjar storepass="secret" jar="${jarfile}" alias="pitchfork">
+ </signjar>
+ </target>
+
+ <target name="all" depends="clean,compile,jar,signjar">
+ </target>
+
+</project>
--- /dev/null
+/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
+/* JOrbisPlayer -- pure Java Ogg Vorbis player
+ *
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec and
+ * JOrbisPlayer depends on JOrbis.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Modified 2007 by Roger Bystrøm for pitchfork <pitchfork@remiss.org>
+ */
+
+import java.util.*;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.applet.*;
+import javax.swing.*;
+
+import com.jcraft.jorbis.*;
+import com.jcraft.jogg.*;
+
+import javax.sound.sampled.*;
+
+public class JOrbisPlayer extends JApplet implements ActionListener {
+
+ Thread player=null;
+ InputStream bitStream=null;
+ URLConnection urlc = null;
+
+ public static final String PLAY = "Play", STOP = "Stop";
+
+ static AppletContext acontext=null;
+
+ static final int BUFSIZE=4096*2;
+ static int convsize=BUFSIZE*2;
+ static byte[] convbuffer=new byte[convsize];
+
+ PlayWatch playThread;
+
+ private int RETRY=3;
+ int retry=RETRY;
+
+ SyncState oy;
+ StreamState os;
+ Page og;
+ Packet op;
+ Info vi;
+ Comment vc;
+ DspState vd;
+ Block vb;
+
+ byte[] buffer=null;
+ int bytes=0;
+
+ int format;
+ int rate=0;
+ int channels=0;
+ int left_vol_scale=100;
+ int right_vol_scale=100;
+ SourceDataLine outputLine=null;
+ String current_source=null;
+
+ int frameSizeInBytes;
+ int bufferLengthInBytes;
+
+ boolean playonstartup=false;
+
+ public Color bgColor = Color.lightGray;
+
+ public void init(){
+
+ playThread = new PlayWatch();
+ playThread.start();
+
+ acontext=getAppletContext();
+
+ loadPlaylist();
+
+ if(playlist.size()>0){
+ String s=getParameter("jorbis.player.playonstartup");
+ if(s!=null && s.equals("yes"))
+ playonstartup=true;
+ }
+
+ String c = getParameter("jorbis.player.bgcolor");
+
+ try {
+ bgColor = new Color(Integer.parseInt(c));
+ } catch (Exception e) {}
+
+ System.out.println("Bg-color: specified: " +c + ", using: " + bgColor.toString());
+
+ initUI();
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(panel);
+ setBackground(bgColor);
+ repaint();
+
+ }
+
+ public void start(){
+ super.start();
+ if(playonstartup){
+ play_sound();
+ }
+ }
+
+ void init_jorbis(){
+ oy=new SyncState();
+ os=new StreamState();
+ og=new Page();
+ op=new Packet();
+
+ vi=new Info();
+ vc=new Comment();
+ vd=new DspState();
+ vb=new Block(vd);
+
+ buffer=null;
+ bytes=0;
+
+ oy.init();
+ }
+
+ public void closeOutputLine() {
+ if(outputLine!=null){
+ //outputLine.drain();
+ outputLine.stop();
+ outputLine.flush();
+ outputLine.close();
+ outputLine = null;
+ }
+ }
+
+ public void closeBitStream() {
+ if(bitStream!=null) {
+ try {
+ bitStream.close();
+ } catch(Exception ee) {}
+ }
+ }
+
+ SourceDataLine getOutputLine(int channels, int rate) throws Exception {
+ if(outputLine==null || this.rate!=rate || this.channels!=channels){
+ closeOutputLine();
+ init_audio(channels, rate);
+ outputLine.start();
+ }
+ return outputLine;
+ }
+
+ void init_audio(int channels, int rate) throws Exception {
+ try {
+
+ AudioFormat audioFormat =
+ new AudioFormat((float)rate,
+ 16,
+ channels,
+ true, // PCM_Signed
+ false // littleEndian
+ );
+ DataLine.Info info =
+ new DataLine.Info(SourceDataLine.class,
+ audioFormat,
+ AudioSystem.NOT_SPECIFIED);
+ if (!AudioSystem.isLineSupported(info)) {
+ //System.out.println("Line " + info + " not supported.");
+ return;
+ }
+
+ try{
+ outputLine = (SourceDataLine) AudioSystem.getLine(info);
+ //outputLine.addLineListener(this);
+ outputLine.open(audioFormat);
+ } catch (LineUnavailableException ex) {
+ System.out.println("Unable to open the sourceDataLine: " + ex);
+ if(acontext != null)
+ acontext.showStatus("Streaming applet: unable to open output device");
+ throw ex;
+ }
+
+ frameSizeInBytes = audioFormat.getFrameSize();
+ int bufferLengthInFrames = outputLine.getBufferSize()/frameSizeInBytes/2;
+ bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
+
+ this.rate=rate;
+ this.channels=channels;
+ }
+ catch(Exception ee){
+ System.out.println(ee);
+ closeOutputLine();
+ throw ee;
+ }
+ }
+
+ private void play_stream(Thread me) {
+
+ boolean chained=false;
+
+ init_jorbis();
+
+ retry=RETRY;
+
+ loop:
+ while(true){
+ int eos=0;
+
+ int index=oy.buffer(BUFSIZE);
+ buffer=oy.data;
+ try{
+ bytes=bitStream.read(buffer, index, BUFSIZE);
+ }
+ catch(Exception e){
+ System.err.println(e);
+ return;
+ }
+ oy.wrote(bytes);
+
+ if(chained){
+ chained=false;
+ }
+ else {
+ if(oy.pageout(og)!=1){
+ if(bytes<BUFSIZE)break;
+ System.err.println("Input does not appear to be an Ogg bitstream.");
+ return;
+ }
+ }
+ os.init(og.serialno());
+ os.reset();
+
+ vi.init();
+ vc.init();
+
+ if(os.pagein(og)<0){
+ // error; stream version mismatch perhaps
+ System.err.println("Error reading first page of Ogg bitstream data.");
+ return;
+ }
+
+ retry=RETRY;
+
+ if(os.packetout(op)!=1){
+ // no page? must not be vorbis
+ System.err.println("Error reading initial header packet.");
+ break;
+ }
+
+ if(vi.synthesis_headerin(vc, op)<0){
+ // error case; not a vorbis header
+ System.err.println("This Ogg bitstream does not contain Vorbis audio data.");
+ return;
+ }
+
+ int i=0;
+
+ while(i<2){
+ while(i<2){
+ int result=oy.pageout(og);
+ if(result==0) break; // Need more data
+ if(result==1){
+ os.pagein(og);
+ while(i<2){
+ result=os.packetout(op);
+ if(result==0)break;
+ if(result==-1){
+ System.err.println("Corrupt secondary header. Exiting.");
+ //return;
+ break loop;
+ }
+ vi.synthesis_headerin(vc, op);
+ i++;
+ }
+ }
+ }
+
+ index=oy.buffer(BUFSIZE);
+ buffer=oy.data;
+ try{ bytes=bitStream.read(buffer, index, BUFSIZE); }
+ catch(Exception e){
+ System.err.println(e);
+ return;
+ }
+ if(bytes==0 && i<2){
+ System.err.println("End of file before finding all Vorbis headers!");
+ return;
+ }
+ oy.wrote(bytes);
+ }
+
+ {
+ byte[][] ptr=vc.user_comments;
+ StringBuffer sb=null;
+ if(acontext!=null) sb=new StringBuffer();
+ String artist = null, title = null, album = null, tmp;
+ for(int j=0; j<ptr.length;j++){
+ if(ptr[j]==null) break;
+ tmp = new String(ptr[j], 0, ptr[j].length-1);
+ System.err.println("Comment: "+tmp);
+ if(sb!=null) {
+ if(tmp.startsWith("ARTIST"))
+ artist = new String(ptr[j], 7, ptr[j].length-8);
+ else if(tmp.startsWith("TITLE"))
+ title = new String(ptr[j], 6, ptr[j].length-7);
+ else if(tmp.startsWith("ALBUM"))
+ album = new String(ptr[j], 6, ptr[j].length-7);
+ else
+ sb.append(" "+tmp);
+ }
+ }
+ System.err.println("Bitstream is "+vi.channels+" channel, "+vi.rate+"Hz");
+ System.err.println("Encoded by: "+new String(vc.vendor, 0, vc.vendor.length-1)+"\n");
+ if(acontext!=null) {
+ StringBuffer stat = new StringBuffer();
+ if(title!=null) {
+ stat.append(title);
+ if(album!=null) {
+ stat.append(" on ");
+ stat.append(album);
+ }
+
+ }
+ else if(album!=null){ // hmm
+ stat.append("Album ");
+ stat.append(album);
+ }
+
+ if(artist!=null) {
+ stat.append(" by ");
+ stat.append(artist);
+ }
+
+ if(sb.length()>0)
+ stat.append(" " +sb);
+ acontext.showStatus(stat.toString());
+ }
+ }
+
+ convsize=BUFSIZE/vi.channels;
+
+ vd.synthesis_init(vi);
+ vb.init(vd);
+
+ float[][][] _pcmf=new float[1][][];
+ int[] _index=new int[vi.channels];
+
+ try {
+ getOutputLine(vi.channels, vi.rate);
+ } catch(Exception e) {
+ stop_sound();
+ return;
+ }
+
+ while(eos==0){
+ while(eos==0){
+
+ if(player!=me){
+ System.err.println("player!=me bye.");
+ closeBitStream();
+ closeOutputLine();
+ if(acontext!=null)
+ acontext.showStatus("");
+ return;
+ }
+
+ int result=oy.pageout(og);
+ if(result==0)break; // need more data
+ if(result==-1){ // missing or corrupt data at this page position
+ //System.err.println("Corrupt or missing data in bitstream; continuing...");
+ }
+ else{
+ os.pagein(og);
+
+ if(og.granulepos()==0){ //
+ chained=true; //
+ eos=1; //
+ break; //
+ } //
+
+ while(true){
+ result=os.packetout(op);
+ if(result==0)break; // need more data
+ if(result==-1){
+ // missing or corrupt data at this page position
+ }
+ else{
+ // we have a packet. Decode it
+ int samples;
+ if(vb.synthesis(op)==0){ // test for success!
+ vd.synthesis_blockin(vb);
+ }
+ while((samples=vd.synthesis_pcmout(_pcmf, _index))>0){
+ float[][] pcmf=_pcmf[0];
+ int bout=(samples<convsize?samples:convsize);
+
+ // convert doubles to 16 bit signed ints (host order) and
+ // interleave
+ for(i=0;i<vi.channels;i++){
+ int ptr=i*2;
+ //int ptr=i;
+ int mono=_index[i];
+ for(int j=0;j<bout;j++){
+ int val=(int)(pcmf[i][mono+j]*32767.);
+ if(val>32767){
+ val=32767;
+ }
+ if(val<-32768){
+ val=-32768;
+ }
+ if(val<0) val=val|0x8000;
+ convbuffer[ptr]=(byte)(val);
+ convbuffer[ptr+1]=(byte)(val>>>8);
+ ptr+=2*(vi.channels);
+ }
+ }
+ outputLine.write(convbuffer, 0, 2*vi.channels*bout);
+ vd.synthesis_read(bout);
+ }
+ }
+ }
+ if(og.eos()!=0)eos=1;
+ }
+ }
+
+ if(eos==0){
+ index=oy.buffer(BUFSIZE);
+ buffer=oy.data;
+ try{ bytes=bitStream.read(buffer,index,BUFSIZE); }
+ catch(Exception e){
+ System.err.println(e);
+ closeOutputLine();
+ return;
+ }
+ if(bytes==-1){
+ break;
+ }
+ oy.wrote(bytes);
+ if(bytes==0)eos=1;
+ }
+ }
+
+ os.clear();
+ vb.clear();
+ vd.clear();
+ vi.clear();
+ }
+
+ closeOutputLine();
+ oy.clear();
+ if(acontext!=null)
+ acontext.showStatus("");
+
+ closeBitStream();
+ }
+
+ public void stop(){
+ if(player==null){
+ closeOutputLine();
+ closeBitStream();
+ }
+ player=null;
+ }
+
+ Vector playlist=new Vector();
+
+ public void actionPerformed(ActionEvent e){
+ String command=((JButton)(e.getSource())).getText();
+ if(command.equals(PLAY) && player==null){
+ play_sound();
+ }
+ else if(player!=null){
+ stop_sound();
+ }
+ }
+
+ private void playFromOwnThread() {
+ synchronized(playThread) {
+ playThread.notify();
+ }
+ }
+
+ private void playFromThisThread() {
+ if(player!=null)
+ return;
+ /*player=new Thread(this);
+ player.start();*/
+ // todo
+ start_button.setText(STOP);
+ String item=getShoutSource();
+ if(item==null) {
+ stop_sound();
+ return;
+ }
+ bitStream=selectSource(item);
+ player = Thread.currentThread();
+ if(bitStream!=null){
+ play_stream(player);
+ }
+ else System.out.println("Bitstream is null");
+ bitStream=null;
+
+ stop_sound();
+ }
+
+
+ /* hooks */
+
+ public boolean isPlaying() {
+ return player != null;
+ }
+
+ public void play_sound(){
+ playFromOwnThread();
+ }
+
+ public void stop_sound(){
+ player=null;
+ start_button.setText(PLAY);
+
+ }
+
+ InputStream selectSource(String item){
+ if(item.endsWith(".pls")){
+ item=fetch_pls(item);
+ if(item==null) return null;
+ //System.out.println("fetch: "+item);
+ }
+ else if(item.endsWith(".m3u")){
+ item=fetch_m3u(item);
+ if(item==null)return null;
+ //System.out.println("fetch: "+item);
+ }
+
+ if(!item.endsWith(".ogg")){
+ return null;
+ }
+
+ InputStream is=null;
+ URLConnection urlc=null;
+ try{
+ URL url=null;
+ url=new URL(getCodeBase(), item);
+ urlc=url.openConnection();
+ urlc.setRequestProperty("Pragma", "no-cache");
+ urlc.setUseCaches(false);
+ is=urlc.getInputStream();
+ current_source=url.getProtocol()+"://"+url.getHost()+":"+url.getPort()+url.getFile();
+ }
+ catch(Exception ee){
+ System.err.println(ee);
+ }
+
+ if(is==null) {
+ System.out.println("Selected input stream is null");
+ return null;
+ }
+
+ System.out.println("Select: "+item);
+ return is;
+ }
+
+ String fetch_pls(String pls){
+ InputStream pstream=null;
+ if(pls.startsWith("http://")){
+ try{
+ URL url=null;
+ url=new URL(getCodeBase(), pls);
+ urlc=url.openConnection();
+ pstream=urlc.getInputStream();
+ }
+ catch(Exception ee){
+ System.err.println(ee);
+ return null;
+ }
+ }
+
+ String line=null;
+ while(true){
+ try{line=readline(pstream);}catch(Exception e){}
+ if(line==null)break;
+ if(line.startsWith("File1=")){
+ byte[] foo=line.getBytes();
+ int i=6;
+ for(;i<foo.length; i++){
+ if(foo[i]==0x0d)break;
+ }
+ return line.substring(6, i);
+ }
+ }
+ return null;
+ }
+
+ String fetch_m3u(String m3u){
+ InputStream pstream=null;
+ if(m3u.startsWith("http://")){
+ try{
+ URL url=null;
+ url=new URL(getCodeBase(), m3u);
+
+ URLConnection urlc=url.openConnection();
+ pstream=urlc.getInputStream();
+ }
+ catch(Exception ee){
+ System.err.println(ee);
+ return null;
+ }
+ }
+
+ String line=null;
+ while(true){
+ try{line=readline(pstream);}catch(Exception e){}
+ if(line==null)break;
+ return line;
+ }
+ return null;
+ }
+
+
+ void loadPlaylist(){
+ String s=null;
+ for(int i=0; i<10; i++){
+ s=getParameter("jorbis.player.play."+i);
+ System.out.println("Play" + i + ": " + s);
+ if(s==null)
+ break;
+ playlist.addElement(s);
+ }
+ }
+
+ private String readline(InputStream is) {
+ StringBuffer rtn=new StringBuffer();
+ int temp;
+ do {
+ try {
+ temp=is.read();
+ }
+ catch(Exception e) {
+ return null;
+ }
+ if(temp==-1)
+ return null;
+ if(temp!=0 && temp!='\n')
+ rtn.append((char)temp);
+ }while(temp!='\n');
+ return(rtn.toString());
+ }
+
+ public JOrbisPlayer(){
+ }
+
+ JPanel panel;
+ JButton start_button;
+
+ void initUI(){
+ panel=new JPanel();
+
+ start_button=new JButton(PLAY);
+ start_button.addActionListener(this);
+ panel.add(start_button);
+ panel.setBackground(bgColor);
+ }
+
+ public String getShoutSource() {
+ try {
+ return (String)playlist.firstElement();
+ }
+ catch(NoSuchElementException e) {
+ return null;
+ }
+ }
+
+ /* since js don't have proper access right's we'll need a seperate watcher thread */
+ public class PlayWatch extends Thread {
+
+ public PlayWatch() { }
+
+ public void run() {
+ while(true) {
+ synchronized(this) {
+ try {
+ this.wait();
+ }catch (InterruptedException e) {}
+ }
+ playFromThisThread();
+ }
+ }
+ }
+}
--- /dev/null
+/* -*-mode:java; c-basic-offset:2; -*- */
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class Buffer{
+ private static final int BUFFER_INCREMENT=256;
+
+ private static final int[] mask={
+ 0x00000000,0x00000001,0x00000003,0x00000007,0x0000000f,
+ 0x0000001f,0x0000003f,0x0000007f,0x000000ff,0x000001ff,
+ 0x000003ff,0x000007ff,0x00000fff,0x00001fff,0x00003fff,
+ 0x00007fff,0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff,
+ 0x000fffff,0x001fffff,0x003fffff,0x007fffff,0x00ffffff,
+ 0x01ffffff,0x03ffffff,0x07ffffff,0x0fffffff,0x1fffffff,
+ 0x3fffffff,0x7fffffff,0xffffffff
+ };
+
+ int ptr=0;
+ byte[] buffer=null;
+ int endbit=0;
+ int endbyte=0;
+ int storage=0;
+
+ public void writeinit(){
+ buffer=new byte[BUFFER_INCREMENT];
+ ptr=0;
+ buffer[0]=(byte)'\0';
+ storage=BUFFER_INCREMENT;
+ }
+
+ public void write(byte[] s){
+ for(int i=0; i<s.length; i++){
+ if(s[i]==0)break;
+ write(s[i],8);
+ }
+ }
+
+ public void read(byte[] s, int bytes){
+ int i=0;
+ while(bytes--!=0){
+ s[i++]=(byte)(read(8));
+ }
+ }
+
+ void reset(){
+ ptr=0;
+ buffer[0]=(byte)'\0';
+ endbit=endbyte=0;
+ }
+
+ public void writeclear(){
+ buffer=null;
+ }
+
+ public void readinit(byte[] buf, int bytes){
+ readinit(buf, 0, bytes);
+ }
+
+ public void readinit(byte[] buf, int start, int bytes){
+//System.err.println("readinit: start="+start+", bytes="+bytes);
+//for(int i=0;i<bytes; i++){
+//System.err.println(i+": "+Integer.toHexString(buf[i+start]));
+//}
+ ptr=start;
+ buffer=buf;
+ endbit=endbyte=0;
+ storage=bytes;
+ }
+
+ public void write(int value, int bits){
+//System.err.println("write: "+Integer.toHexString(value)+", bits="+bits+" ptr="+ptr+", storage="+storage+", endbyte="+endbyte);
+ if(endbyte+4>=storage){
+ byte[] foo=new byte[storage+BUFFER_INCREMENT];
+ System.arraycopy(buffer, 0, foo, 0, storage);
+ buffer=foo;
+ storage+=BUFFER_INCREMENT;
+ }
+
+ value&=mask[bits];
+ bits+=endbit;
+ buffer[ptr]|=(byte)(value<<endbit);
+
+ if(bits>=8){
+ buffer[ptr+1]=(byte)(value>>>(8-endbit));
+ if(bits>=16){
+ buffer[ptr+2]=(byte)(value>>>(16-endbit));
+ if(bits>=24){
+ buffer[ptr+3]=(byte)(value>>>(24-endbit));
+ if(bits>=32){
+ if(endbit>0)
+ buffer[ptr+4]=(byte)(value>>>(32-endbit));
+ else
+ buffer[ptr+4]=0;
+ }
+ }
+ }
+ }
+
+ endbyte+=bits/8;
+ ptr+=bits/8;
+ endbit=bits&7;
+ }
+
+ public int look(int bits){
+ int ret;
+ int m=mask[bits];
+
+ bits+=endbit;
+
+//System.err.println("look ptr:"+ptr+", bits="+bits+", endbit="+endbit+", storage="+storage);
+
+ if(endbyte+4>=storage){
+ if(endbyte+(bits-1)/8>=storage)return(-1);
+ }
+
+ ret=((buffer[ptr])&0xff)>>>endbit;
+// ret=((byte)(buffer[ptr]))>>>endbit;
+ if(bits>8){
+ ret|=((buffer[ptr+1])&0xff)<<(8-endbit);
+// ret|=((byte)(buffer[ptr+1]))<<(8-endbit);
+ if(bits>16){
+ ret|=((buffer[ptr+2])&0xff)<<(16-endbit);
+// ret|=((byte)(buffer[ptr+2]))<<(16-endbit);
+ if(bits>24){
+ ret|=((buffer[ptr+3])&0xff)<<(24-endbit);
+//System.err.print("ret="+Integer.toHexString(ret)+", ((byte)(buffer[ptr+3]))="+Integer.toHexString(((buffer[ptr+3])&0xff)));
+// ret|=((byte)(buffer[ptr+3]))<<(24-endbit);
+//System.err.println(" ->ret="+Integer.toHexString(ret));
+ if(bits>32 && endbit!=0){
+ ret|=((buffer[ptr+4])&0xff)<<(32-endbit);
+// ret|=((byte)(buffer[ptr+4]))<<(32-endbit);
+ }
+ }
+ }
+ }
+ return(m&ret);
+ }
+
+ public int look1(){
+ if(endbyte>=storage)return(-1);
+ return((buffer[ptr]>>endbit)&1);
+ }
+
+ public void adv(int bits){
+ bits+=endbit;
+ ptr+=bits/8;
+ endbyte+=bits/8;
+ endbit=bits&7;
+ }
+
+ public void adv1(){
+ ++endbit;
+ if(endbit>7){
+ endbit=0;
+ ptr++;
+ endbyte++;
+ }
+ }
+
+ public int read(int bits){
+//System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte);
+//System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte+
+// ", ptr="+ptr+", endbit="+endbit+", buf[ptr]="+buffer[ptr]);
+
+ int ret;
+ int m=mask[bits];
+
+ bits+=endbit;
+
+ if(endbyte+4>=storage){
+ ret=-1;
+ if(endbyte+(bits-1)/8>=storage){
+ ptr+=bits/8;
+ endbyte+=bits/8;
+ endbit=bits&7;
+ return(ret);
+ }
+ }
+
+/*
+ ret=(byte)(buffer[ptr]>>>endbit);
+ if(bits>8){
+ ret|=(buffer[ptr+1]<<(8-endbit));
+ if(bits>16){
+ ret|=(buffer[ptr+2]<<(16-endbit));
+ if(bits>24){
+ ret|=(buffer[ptr+3]<<(24-endbit));
+ if(bits>32 && endbit>0){
+ ret|=(buffer[ptr+4]<<(32-endbit));
+ }
+ }
+ }
+ }
+*/
+ ret=((buffer[ptr])&0xff)>>>endbit;
+ if(bits>8){
+ ret|=((buffer[ptr+1])&0xff)<<(8-endbit);
+// ret|=((byte)(buffer[ptr+1]))<<(8-endbit);
+ if(bits>16){
+ ret|=((buffer[ptr+2])&0xff)<<(16-endbit);
+// ret|=((byte)(buffer[ptr+2]))<<(16-endbit);
+ if(bits>24){
+ ret|=((buffer[ptr+3])&0xff)<<(24-endbit);
+// ret|=((byte)(buffer[ptr+3]))<<(24-endbit);
+ if(bits>32 && endbit!=0){
+ ret|=((buffer[ptr+4])&0xff)<<(32-endbit);
+// ret|=((byte)(buffer[ptr+4]))<<(32-endbit);
+ }
+ }
+ }
+ }
+
+ ret&=m;
+
+ ptr+=bits/8;
+// ptr=bits/8;
+ endbyte+=bits/8;
+// endbyte=bits/8;
+ endbit=bits&7;
+ return(ret);
+ }
+
+ public int readB(int bits){
+ //System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte+
+ // ", ptr="+ptr+", endbit="+endbit+", buf[ptr]="+buffer[ptr]);
+ int ret;
+ int m=32-bits;
+
+ bits+=endbit;
+
+ if(endbyte+4>=storage){
+ /* not the main path */
+ ret=-1;
+ if(endbyte*8+bits>storage*8) {
+ ptr+=bits/8;
+ endbyte+=bits/8;
+ endbit=bits&7;
+ return(ret);
+ }
+ }
+
+ ret=(buffer[ptr]&0xff)<<(24+endbit);
+ if(bits>8){
+ ret|=(buffer[ptr+1]&0xff)<<(16+endbit);
+ if(bits>16){
+ ret|=(buffer[ptr+2]&0xff)<<(8+endbit);
+ if(bits>24){
+ ret|=(buffer[ptr+3]&0xff)<<(endbit);
+ if(bits>32 && (endbit != 0))
+ ret|=(buffer[ptr+4]&0xff)>>(8-endbit);
+ }
+ }
+ }
+ ret=(ret>>>(m>>1))>>>((m+1)>>1);
+
+ ptr+=bits/8;
+ endbyte+=bits/8;
+ endbit=bits&7;
+ return(ret);
+ }
+
+ public int read1(){
+ int ret;
+ if(endbyte>=storage){
+ ret=-1;
+ endbit++;
+ if(endbit>7){
+ endbit=0;
+ ptr++;
+ endbyte++;
+ }
+ return(ret);
+ }
+
+ ret=(buffer[ptr]>>endbit)&1;
+
+ endbit++;
+ if(endbit>7){
+ endbit=0;
+ ptr++;
+ endbyte++;
+ }
+ return(ret);
+ }
+
+ public int bytes(){
+ return(endbyte+(endbit+7)/8);
+ }
+
+ public int bits(){
+ return(endbyte*8+endbit);
+ }
+
+ public byte[] buffer(){
+ return(buffer);
+ }
+
+ public static int ilog(int v){
+ int ret=0;
+ while(v>0){
+ ret++;
+ v>>>=1;
+ }
+ return(ret);
+ }
+
+ public static void report(String in){
+ System.err.println(in);
+ System.exit(1);
+ }
+
+ /*
+ static void cliptest(int[] b, int vals, int bits, int[] comp, int compsize){
+ int bytes;
+ byte[] buffer;
+
+ o.reset();
+ for(int i=0;i<vals;i++){
+ o.write(b[i],((bits!=0)?bits:ilog(b[i])));
+ }
+ buffer=o.buffer();
+ bytes=o.bytes();
+System.err.println("cliptest: bytes="+bytes);
+ if(bytes!=compsize)report("wrong number of bytes!\n");
+ for(int i=0;i<bytes;i++){
+ if(buffer[i]!=(byte)comp[i]){
+ for(int j=0;j<bytes;j++){
+ System.err.println(j+": "+Integer.toHexString(buffer[j])+" "+
+ Integer.toHexString(comp[j]));
+ }
+ report("wrote incorrect value!\n");
+ }
+ }
+System.err.println("bits: "+bits);
+ r.readinit(buffer,bytes);
+ for(int i=0;i<vals;i++){
+ int tbit=(bits!=0)?bits:ilog(b[i]);
+System.err.println(Integer.toHexString(b[i])+" tbit: "+tbit);
+ if(r.look(tbit)==-1){
+ report("out of data!\n");
+ }
+ if(r.look(tbit)!=(b[i]&mask[tbit])){
+ report(i+" looked at incorrect value! "+Integer.toHexString(r.look(tbit))+", "+Integer.toHexString(b[i]&mask[tbit])+":"+b[i]+" bit="+tbit);
+ }
+ if(tbit==1){
+ if(r.look1()!=(b[i]&mask[tbit])){
+ report("looked at single bit incorrect value!\n");
+ }
+ }
+ if(tbit==1){
+ if(r.read1()!=(b[i]&mask[tbit])){
+ report("read incorrect single bit value!\n");
+ }
+ }
+ else{
+ if(r.read(tbit)!=(b[i]&mask[tbit])){
+ report("read incorrect value!\n");
+ }
+ }
+ }
+ if(r.bytes()!=bytes){
+ report("leftover bytes after read!\n");
+ }
+ }
+
+ static int[] testbuffer1=
+ {18,12,103948,4325,543,76,432,52,3,65,4,56,32,42,34,21,1,23,32,546,456,7,
+ 567,56,8,8,55,3,52,342,341,4,265,7,67,86,2199,21,7,1,5,1,4};
+ static int test1size=43;
+
+ static int[] testbuffer2=
+ {216531625,1237861823,56732452,131,3212421,12325343,34547562,12313212,
+ 1233432,534,5,346435231,14436467,7869299,76326614,167548585,
+ 85525151,0,12321,1,349528352};
+ static int test2size=21;
+
+ static int[] large=
+ {2136531625,2137861823,56732452,131,3212421,12325343,34547562,12313212,
+ 1233432,534,5,2146435231,14436467,7869299,76326614,167548585,
+ 85525151,0,12321,1,2146528352};
+
+ static int[] testbuffer3=
+ {1,0,14,0,1,0,12,0,1,0,0,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,1,1,1,1,0,0,1,
+ 0,1,30,1,1,1,0,0,1,0,0,0,12,0,11,0,1,0,0,1};
+ static int test3size=56;
+
+ static int onesize=33;
+ static int[] one={146,25,44,151,195,15,153,176,233,131,196,65,85,172,47,40,
+ 34,242,223,136,35,222,211,86,171,50,225,135,214,75,172,
+ 223,4};
+
+ static int twosize=6;
+ static int[] two={61,255,255,251,231,29};
+
+ static int threesize=54;
+ static int[] three={169,2,232,252,91,132,156,36,89,13,123,176,144,32,254,
+ 142,224,85,59,121,144,79,124,23,67,90,90,216,79,23,83,
+ 58,135,196,61,55,129,183,54,101,100,170,37,127,126,10,
+ 100,52,4,14,18,86,77,1};
+
+ static int foursize=38;
+ static int[] four={18,6,163,252,97,194,104,131,32,1,7,82,137,42,129,11,72,
+ 132,60,220,112,8,196,109,64,179,86,9,137,195,208,122,169,
+ 28,2,133,0,1};
+
+ static int fivesize=45;
+ static int[] five={169,2,126,139,144,172,30,4,80,72,240,59,130,218,73,62,
+ 241,24,210,44,4,20,0,248,116,49,135,100,110,130,181,169,
+ 84,75,159,2,1,0,132,192,8,0,0,18,22};
+
+ static int sixsize=7;
+ static int[] six={17,177,170,242,169,19,148};
+
+ static Buffer o=new Buffer();
+ static Buffer r=new Buffer();
+
+ public static void main(String[] arg){
+ byte[] buffer;
+ int bytes;
+// o=new Buffer();
+// r=new Buffer();
+
+ o.writeinit();
+
+ System.err.print("\nSmall preclipped packing: ");
+ cliptest(testbuffer1,test1size,0,one,onesize);
+ System.err.print("ok.");
+
+ System.err.print("\nNull bit call: ");
+ cliptest(testbuffer3,test3size,0,two,twosize);
+ System.err.print("ok.");
+
+ System.err.print("\nLarge preclipped packing: ");
+ cliptest(testbuffer2,test2size,0,three,threesize);
+ System.err.print("ok.");
+
+ System.err.print("\n32 bit preclipped packing: ");
+ o.reset();
+ for(int i=0;i<test2size;i++)
+ o.write(large[i],32);
+ buffer=o.buffer();
+ bytes=o.bytes();
+
+
+ r.readinit(buffer,bytes);
+ for(int i=0;i<test2size;i++){
+ if(r.look(32)==-1){
+ report("out of data. failed!");
+ }
+ if(r.look(32)!=large[i]){
+ System.err.print(r.look(32)+" != "+large[i]+" ("+
+ Integer.toHexString(r.look(32))+"!="+
+ Integer.toHexString(large[i])+")");
+ report("read incorrect value!\n");
+ }
+ r.adv(32);
+ }
+ if(r.bytes()!=bytes)report("leftover bytes after read!\n");
+ System.err.print("ok.");
+
+ System.err.print("\nSmall unclipped packing: ");
+ cliptest(testbuffer1,test1size,7,four,foursize);
+ System.err.print("ok.");
+
+ System.err.print("\nLarge unclipped packing: ");
+ cliptest(testbuffer2,test2size,17,five,fivesize);
+ System.err.print("ok.");
+
+ System.err.print("\nSingle bit unclicpped packing: ");
+ cliptest(testbuffer3,test3size,1,six,sixsize);
+ System.err.print("ok.");
+
+ System.err.print("\nTesting read past end: ");
+ r.readinit("\0\0\0\0\0\0\0\0".getBytes(),8);
+ for(int i=0;i<64;i++){
+ if(r.read(1)!=0){
+ System.err.print("failed; got -1 prematurely.\n");
+ System.exit(1);
+ }
+ }
+
+ if(r.look(1)!=-1 ||
+ r.read(1)!=-1){
+ System.err.print("failed; read past end without -1.\n");
+ System.exit(1);
+ }
+
+ r.readinit("\0\0\0\0\0\0\0\0".getBytes(),8);
+ if(r.read(30)!=0 || r.read(16)!=0){
+ System.err.print("failed 2; got -1 prematurely.\n");
+ System.exit(1);
+ }
+
+ if(r.look(18)!=0 ||
+ r.look(18)!=0){
+ System.err.print("failed 3; got -1 prematurely.\n");
+ System.exit(1);
+ }
+ if(r.look(19)!=-1 ||
+ r.look(19)!=-1){
+ System.err.print("failed; read past end without -1.\n");
+ System.exit(1);
+ }
+ if(r.look(32)!=-1 ||
+ r.look(32)!=-1){
+ System.err.print("failed; read past end without -1.\n");
+ System.exit(1);
+ }
+ System.err.print("ok.\n\n");
+ }
+ */
+}
+
+
+
+
+
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class Packet{
+ public byte[] packet_base;
+ public int packet;
+ public int bytes;
+ public int b_o_s;
+ public int e_o_s;
+
+ public long granulepos;
+
+ public long packetno; // sequence number for decode; the framing
+ // knows where there's a hole in the data,
+ // but we need coupling so that the codec
+ // (which is in a seperate abstraction
+ // layer) also knows about the gap
+
+ /*
+ // TEST
+ static int sequence=0;
+ static int lastno=0;
+ void checkpacket(int len, int no, int pos){
+ if(bytes!=len){
+ System.err.println("incorrect packet length!");
+ System.exit(1);
+ }
+ if(granulepos!=pos){
+ System.err.println("incorrect packet position!");
+ System.exit(1);
+ }
+
+ // packet number just follows sequence/gap; adjust the input number
+ // for that
+ if(no==0){
+ sequence=0;
+ }
+ else{
+ sequence++;
+ if(no>lastno+1)
+ sequence++;
+ }
+ lastno=no;
+ if(packetno!=sequence){
+ System.err.println("incorrect packet sequence "+packetno+" != "+sequence);
+ System.exit(1);
+ }
+
+ // Test data
+ for(int j=0;j<bytes;j++){
+ if((packet_base[packet+j]&0xff)!=((j+no)&0xff)){
+ System.err.println("body data mismatch at pos "+ j+": "+(packet_base[packet+j]&0xff)+"!="+((j+no)&0xff)+"!\n");
+ System.exit(1);
+ }
+ }
+ }
+ */
+}
--- /dev/null
+/* -*-mode:java; c-basic-offset:2; -*- */
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class Page{
+ private static int[] crc_lookup=new int[256];
+ static {
+ for(int i=0; i<crc_lookup.length; i++){
+ crc_lookup[i]=crc_entry(i);
+ }
+ }
+
+ private static int crc_entry(int index){
+ int r=index<<24;
+ for(int i=0; i<8; i++){
+ if((r& 0x80000000)!=0){
+ r=(r << 1)^0x04c11db7; /* The same as the ethernet generator
+ polynomial, although we use an
+ unreflected alg and an init/final
+ of 0, not 0xffffffff */
+ }
+ else{
+ r<<=1;
+ }
+ }
+ return(r&0xffffffff);
+ }
+
+ public byte[] header_base;
+ public int header;
+ public int header_len;
+ public byte[] body_base;
+ public int body;
+ public int body_len;
+
+ int version(){
+ return header_base[header+4]&0xff;
+ }
+ int continued(){
+ return (header_base[header+5]&0x01);
+ }
+ public int bos(){
+ return (header_base[header+5]&0x02);
+ }
+ public int eos(){
+ return (header_base[header+5]&0x04);
+ }
+ public long granulepos(){
+ long foo=header_base[header+13]&0xff;
+ foo=(foo<<8)|(header_base[header+12]&0xff);
+ foo=(foo<<8)|(header_base[header+11]&0xff);
+ foo=(foo<<8)|(header_base[header+10]&0xff);
+ foo=(foo<<8)|(header_base[header+9]&0xff);
+ foo=(foo<<8)|(header_base[header+8]&0xff);
+ foo=(foo<<8)|(header_base[header+7]&0xff);
+ foo=(foo<<8)|(header_base[header+6]&0xff);
+ return(foo);
+ }
+ public int serialno(){
+ return (header_base[header+14]&0xff)|
+ ((header_base[header+15]&0xff)<<8)|
+ ((header_base[header+16]&0xff)<<16)|
+ ((header_base[header+17]&0xff)<<24);
+ }
+ int pageno(){
+ return (header_base[header+18]&0xff)|
+ ((header_base[header+19]&0xff)<<8)|
+ ((header_base[header+20]&0xff)<<16)|
+ ((header_base[header+21]&0xff)<<24);
+ }
+
+ void checksum(){
+ int crc_reg=0;
+
+// for(int i=0;i<header_len;i++){
+// System.err.println("chksum: "+Integer.toHexString(header_base[header+i]&0xff));
+// }
+
+ for(int i=0;i<header_len;i++){
+ crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg>>>24)&0xff)^(header_base[header+i]&0xff)];
+ }
+ for(int i=0;i<body_len;i++){
+ crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg>>>24)&0xff)^(body_base[body+i]&0xff)];
+ }
+ header_base[header+22]=(byte)crc_reg/*&0xff*/;
+ header_base[header+23]=(byte)(crc_reg>>>8)/*&0xff*/;
+ header_base[header+24]=(byte)(crc_reg>>>16)/*&0xff*/;
+ header_base[header+25]=(byte)(crc_reg>>>24)/*&0xff*/;
+ }
+ public Page copy(){
+ return copy(new Page());
+ }
+ public Page copy(Page p){
+ byte[] tmp=new byte[header_len];
+ System.arraycopy(header_base, header, tmp, 0, header_len);
+ p.header_len=header_len;
+ p.header_base=tmp;
+ p.header=0;
+ tmp=new byte[body_len];
+ System.arraycopy(body_base, body, tmp, 0, body_len);
+ p.body_len=body_len;
+ p.body_base=tmp;
+ p.body=0;
+ return p;
+ }
+ /*
+ // TEST
+ static StreamState os_en, os_de;
+ static SyncState oy;
+ void check_page(byte[] data_base, int data, int[] _header){
+ // Test data
+ for(int j=0;j<body_len;j++)
+ if(body_base[body+j]!=data_base[data+j]){
+ System.err.println("body data mismatch at pos "+j+": "+data_base[data+j]+"!="+body_base[body+j]+"!\n");
+ System.exit(1);
+ }
+
+ // Test header
+ for(int j=0;j<header_len;j++){
+ if((header_base[header+j]&0xff)!=_header[j]){
+ System.err.println("header content mismatch at pos "+j);
+ for(int jj=0;jj<_header[26]+27;jj++)
+ System.err.print(" ("+jj+")"+Integer.toHexString(_header[jj])+":"+Integer.toHexString(header_base[header+jj]));
+ System.err.println("");
+ System.exit(1);
+ }
+ }
+ if(header_len!=_header[26]+27){
+ System.err.print("header length incorrect! ("+header_len+"!="+(_header[26]+27)+")");
+ System.exit(1);
+ }
+ }
+
+ void print_header(){
+ System.err.println("\nHEADER:");
+ System.err.println(" capture: "+
+ (header_base[header+0]&0xff)+" "+
+ (header_base[header+1]&0xff)+" "+
+ (header_base[header+2]&0xff)+" "+
+ (header_base[header+3]&0xff)+" "+
+ " version: "+(header_base[header+4]&0xff)+" flags: "+
+ (header_base[header+5]&0xff));
+ System.err.println(" pcmpos: "+
+ (((header_base[header+9]&0xff)<<24)|
+ ((header_base[header+8]&0xff)<<16)|
+ ((header_base[header+7]&0xff)<<8)|
+ ((header_base[header+6]&0xff)))+
+ " serialno: "+
+ (((header_base[header+17]&0xff)<<24)|
+ ((header_base[header+16]&0xff)<<16)|
+ ((header_base[header+15]&0xff)<<8)|
+ ((header_base[header+14]&0xff)))+
+ " pageno: "+
+ (((header_base[header+21]&0xff)<<24)|
+ ((header_base[header+20]&0xff)<<16)|
+ ((header_base[header+19]&0xff)<<8)|
+ ((header_base[header+18]&0xff))));
+
+ System.err.println(" checksum: "+
+ (header_base[header+22]&0xff)+":"+
+ (header_base[header+23]&0xff)+":"+
+ (header_base[header+24]&0xff)+":"+
+ (header_base[header+25]&0xff)+"\n segments: "+
+ (header_base[header+26]&0xff)+" (");
+ for(int j=27;j<header_len;j++){
+ System.err.println((header_base[header+j]&0xff)+" ");
+ }
+ System.err.println(")\n");
+ }
+
+ void copy_page(){
+ byte[] tmp=new byte[header_len];
+ System.arraycopy(header_base, header, tmp, 0, header_len);
+ header_base=tmp;
+ header=0;
+ tmp=new byte[body_len];
+ System.arraycopy(body_base, body, tmp, 0, body_len);
+ body_base=tmp;
+ body=0;
+ }
+
+ static void test_pack(int[] pl, int[][] headers){
+ byte[] data=new byte[1024*1024]; // for scripted test cases only
+ int inptr=0;
+ int outptr=0;
+ int deptr=0;
+ int depacket=0;
+ int pcm_pos=7;
+ int packets,pageno=0,pageout=0;
+ int eosflag=0;
+ int bosflag=0;
+
+ os_en.reset();
+ os_de.reset();
+ oy.reset();
+
+ for(packets=0;;packets++){
+ if(pl[packets]==-1)break;
+ }
+
+ for(int i=0;i<packets;i++){
+ // construct a test packet
+ Packet op=new Packet();
+ int len=pl[i];
+ op.packet_base=data;
+ op.packet=inptr;
+ op.bytes=len;
+ op.e_o_s=(pl[i+1]<0?1:0);
+ op.granulepos=pcm_pos;
+
+ pcm_pos+=1024;
+
+ for(int j=0;j<len;j++){
+ data[inptr++]=(byte)(i+j);
+ }
+
+ // submit the test packet
+ os_en.packetin(op);
+
+ // retrieve any finished pages
+ {
+ Page og=new Page();
+
+ while(os_en.pageout(og)!=0){
+ // We have a page. Check it carefully
+ //System.err.print(pageno+", ");
+ if(headers[pageno]==null){
+ System.err.println("coded too many pages!");
+ System.exit(1);
+ }
+ og.check_page(data, outptr, headers[pageno]);
+
+ outptr+=og.body_len;
+ pageno++;
+
+//System.err.println("1# pageno="+pageno+", pageout="+pageout);
+
+ // have a complete page; submit it to sync/decode
+
+ {
+ Page og_de=new Page();
+ Packet op_de=new Packet();
+ int index=oy.buffer(og.header_len+og.body_len);
+ byte[] buf=oy.data;
+ System.arraycopy(og.header_base, og.header, buf, index, og.header_len);
+ System.arraycopy(og.body_base, og.body, buf, index+og.header_len, og.body_len);
+ oy.wrote(og.header_len+og.body_len);
+
+//System.err.println("2# pageno="+pageno+", pageout="+pageout);
+
+ while(oy.pageout(og_de)>0){
+ // got a page. Happy happy. Verify that it's good.
+
+ og_de.check_page(data, deptr, headers[pageout]);
+ deptr+=og_de.body_len;
+ pageout++;
+
+ // submit it to deconstitution
+ os_de.pagein(og_de);
+
+ // packets out?
+ while(os_de.packetout(op_de)>0){
+
+ // verify the packet!
+ // check data
+ boolean check=false;
+ for(int ii=0; ii<op_de.bytes; ii++){
+ if(data[depacket+ii]!=op_de.packet_base[op_de.packet+ii]){
+ check=true;
+ break;
+ }
+ }
+ if(check){
+ System.err.println("packet data mismatch in decode! pos="+
+ depacket);
+ System.exit(1);
+ }
+
+ // check bos flag
+ if(bosflag==0 && op_de.b_o_s==0){
+ System.err.println("b_o_s flag not set on packet!");
+ System.exit(1);
+ }
+ if(bosflag!=0 && op_de.b_o_s!=0){
+ System.err.println("b_o_s flag incorrectly set on packet!");
+ System.exit(1);
+ }
+
+ bosflag=1;
+ depacket+=op_de.bytes;
+
+ // check eos flag
+ if(eosflag!=0){
+ System.err.println("Multiple decoded packets with eos flag!");
+ System.exit(1);
+ }
+
+ if(op_de.e_o_s!=0)eosflag=1;
+
+ // check pcmpos flag
+ if(op_de.granulepos!=-1){
+ System.err.print(" pcm:"+op_de.granulepos+" ");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ //free(data);
+ if(headers[pageno]!=null){
+ System.err.println("did not write last page!");
+ System.exit(1);
+ }
+ if(headers[pageout]!=null){
+ System.err.println("did not decode last page!");
+ System.exit(1);
+ }
+ if(inptr!=outptr){
+ System.err.println("encoded page data incomplete!");
+ System.exit(1);
+ }
+ if(inptr!=deptr){
+ System.err.println("decoded page data incomplete!");
+ System.exit(1);
+ }
+ if(inptr!=depacket){
+ System.err.println("decoded packet data incomplete!");
+ System.exit(1);
+ }
+ if(eosflag==0){
+ System.err.println("Never got a packet with EOS set!");
+ }
+ System.err.println("ok.");
+ }
+
+ static void error(){
+ System.err.println("error!");
+ System.exit(1);
+ }
+ public static void main(String[] arg){
+
+ os_en=new StreamState(0x04030201);
+ os_de=new StreamState(0x04030201);
+
+ oy=new SyncState();
+
+ // Exercise each code path in the framing code. Also verify that
+ // the checksums are working.
+
+ {
+ // 17 only
+ int[] packets={17, -1};
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x06,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0x15,0xed,0xec,0x91,
+ 1,
+ 17};
+ int[][] headret={head1, null};
+
+ System.err.print("testing single page encoding... ");
+ test_pack(packets,headret);
+ }
+
+ {
+ // 17, 254, 255, 256, 500, 510, 600 byte, pad
+ int[] packets={17, 254, 255, 256, 500, 510, 600, -1};
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0x59,0x10,0x6c,0x2c,
+ 1,
+ 17};
+ int[] head2={0x4f,0x67,0x67,0x53,0,0x04,
+ 0x07,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,1,0,0,0,
+ 0x89,0x33,0x85,0xce,
+ 13,
+ 254,255,0,255,1,255,245,255,255,0,
+ 255,255,90};
+ int[][] headret={head1,head2,null};
+
+ System.err.print("testing basic page encoding... ");
+ test_pack(packets,headret);
+ }
+
+ {
+ // nil packets; beginning,middle,end
+ int[] packets={0,17, 254, 255, 0, 256, 0, 500, 510, 600, 0, -1};
+
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0xff,0x7b,0x23,0x17,
+ 1,
+ 0};
+ int[] head2={0x4f,0x67,0x67,0x53,0,0x04,
+ 0x07,0x28,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,1,0,0,0,
+ 0x5c,0x3f,0x66,0xcb,
+ 17,
+ 17,254,255,0,0,255,1,0,255,245,255,255,0,
+ 255,255,90,0};
+ int[][] headret={head1,head2,null};
+
+ System.err.print("testing basic nil packets... ");
+ test_pack(packets,headret);
+ }
+
+ {
+ // large initial packet
+ int[] packets={4345,259,255,-1};
+
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0x01,0x27,0x31,0xaa,
+ 18,
+ 255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,10};
+
+ int[] head2={0x4f,0x67,0x67,0x53,0,0x04,
+ 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,1,0,0,0,
+ 0x7f,0x4e,0x8a,0xd2,
+ 4,
+ 255,4,255,0};
+ int[][] headret={head1,head2,null};
+
+ System.err.print("testing initial-packet lacing > 4k... ");
+ test_pack(packets,headret);
+ }
+
+ {
+ // continuing packet test
+ int[] packets={0,4345,259,255,-1};
+
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0xff,0x7b,0x23,0x17,
+ 1,
+ 0};
+
+ int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+ 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,1,0,0,0,
+ 0x34,0x24,0xd5,0x29,
+ 17,
+ 255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255};
+
+ int[] head3={0x4f,0x67,0x67,0x53,0,0x05,
+ 0x07,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,2,0,0,0,
+ 0xc8,0xc3,0xcb,0xed,
+ 5,
+ 10,255,4,255,0};
+ int[][] headret={head1,head2,head3,null};
+
+ System.err.print("testing single packet page span... ");
+ test_pack(packets,headret);
+ }
+
+ // page with the 255 segment limit
+ {
+
+ int[] packets={0,10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,50,-1};
+
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0xff,0x7b,0x23,0x17,
+ 1,
+ 0};
+
+ int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+ 0x07,0xfc,0x03,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,1,0,0,0,
+ 0xed,0x2a,0x2e,0xa7,
+ 255,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10,10,
+ 10,10,10,10,10,10,10};
+
+ int[] head3={0x4f,0x67,0x67,0x53,0,0x04,
+ 0x07,0x00,0x04,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,2,0,0,0,
+ 0x6c,0x3b,0x82,0x3d,
+ 1,
+ 50};
+ int[][] headret={head1,head2,head3,null};
+
+ System.err.print("testing max packet segments... ");
+ test_pack(packets,headret);
+ }
+
+ {
+ // packet that overspans over an entire page
+
+ int[] packets={0,100,9000,259,255,-1};
+
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0xff,0x7b,0x23,0x17,
+ 1,
+ 0};
+
+ int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+ 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,1,0,0,0,
+ 0x3c,0xd9,0x4d,0x3f,
+ 17,
+ 100,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255};
+
+ int[] head3={0x4f,0x67,0x67,0x53,0,0x01,
+ 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,2,0,0,0,
+ 0xbd,0xd5,0xb5,0x8b,
+ 17,
+ 255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255};
+
+ int[] head4={0x4f,0x67,0x67,0x53,0,0x05,
+ 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,3,0,0,0,
+ 0xef,0xdd,0x88,0xde,
+ 7,
+ 255,255,75,255,4,255,0};
+ int[][] headret={head1,head2,head3,head4,null};
+
+ System.err.print("testing very large packets... ");
+ test_pack(packets,headret);
+ }
+
+ {
+ // term only page. why not?
+
+ int[] packets={0,100,4080,-1};
+
+ int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,0,0,0,0,
+ 0xff,0x7b,0x23,0x17,
+ 1,
+ 0};
+
+ int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+ 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,1,0,0,0,
+ 0x3c,0xd9,0x4d,0x3f,
+ 17,
+ 100,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255};
+
+ int[] head3={0x4f,0x67,0x67,0x53,0,0x05,
+ 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x03,0x04,2,0,0,0,
+ 0xd4,0xe0,0x60,0xe5,
+ 1,0};
+
+ int[][] headret={head1,head2,head3,null};
+
+ System.err.print("testing zero data page (1 nil packet)... ");
+ test_pack(packets,headret);
+ }
+
+ {
+ // build a bunch of pages for testing
+ byte[] data=new byte[1024*1024];
+ int[] pl={0,100,4079,2956,2057,76,34,912,0,234,1000,1000,1000,300,-1};
+ int inptr=0;
+ Page[] og=new Page[5];
+ for(int i=0; i<5; i++){
+ og[i]=new Page();
+ }
+
+ os_en.reset();
+
+ for(int i=0;pl[i]!=-1;i++){
+ Packet op=new Packet();
+ int len=pl[i];
+
+ op.packet_base=data;
+ op.packet=inptr;
+ op.bytes=len;
+ op.e_o_s=(pl[i+1]<0?1:0);
+ op.granulepos=(i+1)*1000;
+
+ for(int j=0;j<len;j++)data[inptr++]=(byte)(i+j);
+ os_en.packetin(op);
+ }
+
+// free(data);
+
+ // retrieve finished pages
+ for(int i=0;i<5;i++){
+ if(os_en.pageout(og[i])==0){
+ System.err.print("Too few pages output building sync tests!\n");
+ System.exit(1);
+ }
+ og[i].copy_page();
+ }
+
+ // Test lost pages on pagein/packetout: no rollback
+ {
+ Page temp=new Page();
+ Packet test=new Packet();
+
+ System.err.print("Testing loss of pages... ");
+
+ oy.reset();
+ os_de.reset();
+ for(int i=0;i<5;i++){
+ int index=oy.buffer(og[i].header_len);
+ System.arraycopy(og[i].header_base, og[i].header,
+ oy.data, index, og[i].header_len);
+ oy.wrote(og[i].header_len);
+ index=oy.buffer(og[i].body_len);
+ System.arraycopy(og[i].body_base, og[i].body,
+ oy.data, index, og[i].body_len);
+ oy.wrote(og[i].body_len);
+ }
+
+ oy.pageout(temp);
+ os_de.pagein(temp);
+ oy.pageout(temp);
+ os_de.pagein(temp);
+ oy.pageout(temp);
+
+ // skip
+ oy.pageout(temp);
+ os_de.pagein(temp);
+
+ // do we get the expected results/packets?
+
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(0,0,0);
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(100,1,-1);
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(4079,2,3000);
+ if(os_de.packetout(test)!=-1){
+ System.err.println("Error: loss of page did not return error");
+ System.exit(1);
+ }
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(76,5,-1);
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(34,6,-1);
+ System.err.println("ok.");
+ }
+
+ // Test lost pages on pagein/packetout: rollback with continuation
+ {
+ Page temp=new Page();
+ Packet test=new Packet();
+
+ System.err.print("Testing loss of pages (rollback required)... ");
+
+ oy.reset();
+ os_de.reset();
+ for(int i=0;i<5;i++){
+ int index=oy.buffer(og[i].header_len);
+ System.arraycopy(og[i].header_base, og[i].header,
+ oy.data, index, og[i].header_len);
+ oy.wrote(og[i].header_len);
+ index=oy.buffer(og[i].body_len);
+ System.arraycopy(og[i].body_base, og[i].body,
+ oy.data, index, og[i].body_len);
+ oy.wrote(og[i].body_len);
+ }
+
+ oy.pageout(temp);
+ os_de.pagein(temp);
+ oy.pageout(temp);
+ os_de.pagein(temp);
+ oy.pageout(temp);
+ os_de.pagein(temp);
+ oy.pageout(temp);
+ // skip
+ oy.pageout(temp);
+ os_de.pagein(temp);
+
+ // do we get the expected results/packets?
+
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(0,0,0);
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(100,1,-1);
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(4079,2,3000);
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(2956,3,4000);
+ if(os_de.packetout(test)!=-1){
+ System.err.println("Error: loss of page did not return error");
+ System.exit(1);
+ }
+ if(os_de.packetout(test)!=1)error();
+ test.checkpacket(300,13,14000);
+ System.err.println("ok.");
+ }
+
+ // the rest only test sync
+ {
+ Page og_de=new Page();
+ // Test fractional page inputs: incomplete capture
+ System.err.print("Testing sync on partial inputs... ");
+ oy.reset();
+ int index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header,
+ oy.data, index, 3);
+ oy.wrote(3);
+ if(oy.pageout(og_de)>0)error();
+
+ // Test fractional page inputs: incomplete fixed header
+ index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header+3,
+ oy.data, index, 20);
+
+ oy.wrote(20);
+ if(oy.pageout(og_de)>0)error();
+
+ // Test fractional page inputs: incomplete header
+ index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header+23,
+ oy.data, index, 5);
+ oy.wrote(5);
+ if(oy.pageout(og_de)>0)error();
+
+ // Test fractional page inputs: incomplete body
+ index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header+28,
+ oy.data, index, og[1].header_len-28);
+ oy.wrote(og[1].header_len-28);
+ if(oy.pageout(og_de)>0)error();
+
+ index=oy.buffer(og[1].body_len);
+ System.arraycopy(og[1].body_base, og[1].body,
+ oy.data, index, 1000);
+ oy.wrote(1000);
+ if(oy.pageout(og_de)>0)error();
+
+ index=oy.buffer(og[1].body_len);
+ System.arraycopy(og[1].body_base, og[1].body+1000,
+ oy.data, index, og[1].body_len-1000);
+ oy.wrote(og[1].body_len-1000);
+ if(oy.pageout(og_de)<=0)error();
+ System.err.println("ok.");
+ }
+
+ // Test fractional page inputs: page + incomplete capture
+ {
+ Page og_de=new Page();
+ System.err.print("Testing sync on 1+partial inputs... ");
+ oy.reset();
+
+ int index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header,
+ oy.data, index, og[1].header_len);
+ oy.wrote(og[1].header_len);
+
+ index=oy.buffer(og[1].body_len);
+ System.arraycopy(og[1].body_base, og[1].body,
+ oy.data, index, og[1].body_len);
+ oy.wrote(og[1].body_len);
+
+ index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header,
+ oy.data, index, 20);
+ oy.wrote(20);
+ if(oy.pageout(og_de)<=0)error();
+ if(oy.pageout(og_de)>0)error();
+
+ index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header+20,
+ oy.data, index, og[1].header_len-20);
+ oy.wrote(og[1].header_len-20);
+ index=oy.buffer(og[1].body_len);
+ System.arraycopy(og[1].body_base, og[1].body,
+ oy.data, index, og[1].body_len);
+
+ oy.wrote(og[1].body_len);
+ if(oy.pageout(og_de)<=0)error();
+
+ System.err.println("ok.");
+ }
+
+// // // // // // // // //
+ // Test recapture: garbage + page
+ {
+ Page og_de=new Page();
+ System.err.print("Testing search for capture... ");
+ oy.reset();
+
+ // 'garbage'
+ int index=oy.buffer(og[1].body_len);
+ System.arraycopy(og[1].body_base, og[1].body,
+ oy.data, index, og[1].body_len);
+ oy.wrote(og[1].body_len);
+
+ index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header,
+ oy.data, index, og[1].header_len);
+ oy.wrote(og[1].header_len);
+
+ index=oy.buffer(og[1].body_len);
+ System.arraycopy(og[1].body_base, og[1].body,
+ oy.data, index, og[1].body_len);
+ oy.wrote(og[1].body_len);
+
+ index=oy.buffer(og[2].header_len);
+ System.arraycopy(og[2].header_base, og[2].header,
+ oy.data, index, 20);
+
+ oy.wrote(20);
+ if(oy.pageout(og_de)>0)error();
+ if(oy.pageout(og_de)<=0)error();
+ if(oy.pageout(og_de)>0)error();
+
+ index=oy.buffer(og[2].header_len);
+ System.arraycopy(og[2].header_base, og[2].header+20,
+ oy.data, index, og[2].header_len-20);
+ oy.wrote(og[2].header_len-20);
+ index=oy.buffer(og[2].body_len);
+ System.arraycopy(og[2].body_base, og[2].body,
+ oy.data, index, og[2].body_len);
+ oy.wrote(og[2].body_len);
+ if(oy.pageout(og_de)<=0)error();
+
+ System.err.println("ok.");
+ }
+
+ // Test recapture: page + garbage + page
+ {
+ Page og_de=new Page();
+ System.err.print("Testing recapture... ");
+ oy.reset();
+
+ int index=oy.buffer(og[1].header_len);
+ System.arraycopy(og[1].header_base, og[1].header,
+ oy.data, index, og[1].header_len);
+ oy.wrote(og[1].header_len);
+
+ index=oy.buffer(og[1].body_len);
+ System.arraycopy(og[1].body_base, og[1].body,
+ oy.data, index, og[1].body_len);
+ oy.wrote(og[1].body_len);
+
+ index=oy.buffer(og[2].header_len);
+ System.arraycopy(og[2].header_base, og[2].header,
+ oy.data, index, og[2].header_len);
+ oy.wrote(og[2].header_len);
+
+ index=oy.buffer(og[2].header_len);
+ System.arraycopy(og[2].header_base, og[2].header,
+ oy.data, index, og[2].header_len);
+ oy.wrote(og[2].header_len);
+
+ if(oy.pageout(og_de)<=0)error();
+
+ index=oy.buffer(og[2].body_len);
+ System.arraycopy(og[2].body_base, og[2].body,
+ oy.data, index, og[2].body_len-5);
+ oy.wrote(og[2].body_len-5);
+
+ index=oy.buffer(og[3].header_len);
+ System.arraycopy(og[3].header_base, og[3].header,
+ oy.data, index, og[3].header_len);
+ oy.wrote(og[3].header_len);
+
+ index=oy.buffer(og[3].body_len);
+ System.arraycopy(og[3].body_base, og[3].body,
+ oy.data, index, og[3].body_len);
+ oy.wrote(og[3].body_len);
+
+ if(oy.pageout(og_de)>0)error();
+ if(oy.pageout(og_de)<=0)error();
+
+ System.err.println("ok.");
+ }
+ }
+ //return(0);
+ }
+ */
+}
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class StreamState{
+ byte[] body_data; /* bytes from packet bodies */
+ int body_storage; /* storage elements allocated */
+ int body_fill; /* elements stored; fill mark */
+private int body_returned; /* elements of fill returned */
+
+
+ int[] lacing_vals; /* The values that will go to the segment table */
+ long[] granule_vals; /* pcm_pos values for headers. Not compact
+ this way, but it is simple coupled to the
+ lacing fifo */
+ int lacing_storage;
+ int lacing_fill;
+ int lacing_packet;
+ int lacing_returned;
+
+ byte[] header=new byte[282]; /* working space for header encode */
+ int header_fill;
+
+ public int e_o_s; /* set when we have buffered the last packet in the
+ logical bitstream */
+ int b_o_s; /* set after we've written the initial page
+ of a logical bitstream */
+ int serialno;
+ int pageno;
+ long packetno; /* sequence number for decode; the framing
+ knows where there's a hole in the data,
+ but we need coupling so that the codec
+ (which is in a seperate abstraction
+ layer) also knows about the gap */
+ long granulepos;
+
+ public StreamState(){
+ init();
+ }
+
+ StreamState(int serialno){
+ this();
+ init(serialno);
+ }
+ void init(){
+ body_storage=16*1024;
+ body_data=new byte[body_storage];
+ lacing_storage=1024;
+ lacing_vals=new int[lacing_storage];
+ granule_vals=new long[lacing_storage];
+ }
+ public void init(int serialno){
+ if(body_data==null){ init(); }
+ else{
+ for(int i=0; i<body_data.length; i++) body_data[i]=0;
+ for(int i=0; i<lacing_vals.length; i++) lacing_vals[i]=0;
+ for(int i=0; i<granule_vals.length; i++) granule_vals[i]=0;
+ }
+ this.serialno=serialno;
+ }
+ public void clear(){
+ body_data=null;
+ lacing_vals=null;
+ granule_vals=null;
+ //memset(os,0,sizeof(ogg_stream_state));
+ }
+ void destroy(){
+ clear();
+ }
+ void body_expand(int needed){
+ if(body_storage<=body_fill+needed){
+ body_storage+=(needed+1024);
+ byte[] foo=new byte[body_storage];
+ System.arraycopy(body_data, 0, foo, 0, body_data.length);
+ body_data=foo;
+//System.out.println("expand: body_fill="+body_fill+", body_storage="+body_data.length);
+ }
+ }
+ void lacing_expand(int needed){
+ if(lacing_storage<=lacing_fill+needed){
+ lacing_storage+=(needed+32);
+ int[] foo=new int[lacing_storage];
+ System.arraycopy(lacing_vals, 0, foo, 0, lacing_vals.length);
+ lacing_vals=foo;
+
+ long[] bar=new long[lacing_storage];
+ System.arraycopy(granule_vals, 0, bar, 0, granule_vals.length);
+ granule_vals=bar;
+ }
+ }
+
+ /* submit data to the internal buffer of the framing engine */
+ public int packetin(Packet op){
+ int lacing_val=op.bytes/255+1;
+
+ if(body_returned!=0){
+ /* advance packet data according to the body_returned pointer. We
+ had to keep it around to return a pointer into the buffer last
+ call */
+
+ body_fill-=body_returned;
+ if(body_fill!=0){
+// memmove(os->body_data,os->body_data+os->body_returned,
+// os->body_fill*sizeof(char));
+ System.arraycopy(body_data, body_returned, body_data, 0, body_fill);
+ }
+ body_returned=0;
+ }
+
+ /* make sure we have the buffer storage */
+ body_expand(op.bytes);
+ lacing_expand(lacing_val);
+
+ /* Copy in the submitted packet. Yes, the copy is a waste; this is
+ the liability of overly clean abstraction for the time being. It
+ will actually be fairly easy to eliminate the extra copy in the
+ future */
+
+ System.arraycopy(op.packet_base, op.packet, body_data, body_fill, op.bytes);
+ body_fill+=op.bytes;
+//System.out.println("add: "+body_fill);
+
+ /* Store lacing vals for this packet */
+ int j;
+ for(j=0;j<lacing_val-1;j++){
+ lacing_vals[lacing_fill+j]=255;
+ granule_vals[lacing_fill+j]=granulepos;
+ }
+ lacing_vals[lacing_fill+j]=(op.bytes)%255;
+ granulepos=granule_vals[lacing_fill+j]=op.granulepos;
+
+ /* flag the first segment as the beginning of the packet */
+ lacing_vals[lacing_fill]|= 0x100;
+
+ lacing_fill+=lacing_val;
+
+ /* for the sake of completeness */
+ packetno++;
+
+ if(op.e_o_s!=0)e_o_s=1;
+ return(0);
+ }
+
+ public int packetout(Packet op){
+
+ /* The last part of decode. We have the stream broken into packet
+ segments. Now we need to group them into packets (or return the
+ out of sync markers) */
+
+ int ptr=lacing_returned;
+
+ if(lacing_packet<=ptr){
+ return(0);
+ }
+
+ if((lacing_vals[ptr]&0x400)!=0){
+ /* We lost sync here; let the app know */
+ lacing_returned++;
+
+ /* we need to tell the codec there's a gap; it might need to
+ handle previous packet dependencies. */
+ packetno++;
+ return(-1);
+ }
+
+ /* Gather the whole packet. We'll have no holes or a partial packet */
+ {
+ int size=lacing_vals[ptr]&0xff;
+ int bytes=0;
+
+ op.packet_base=body_data;
+ op.packet=body_returned;
+ op.e_o_s=lacing_vals[ptr]&0x200; /* last packet of the stream? */
+ op.b_o_s=lacing_vals[ptr]&0x100; /* first packet of the stream? */
+ bytes+=size;
+
+ while(size==255){
+ int val=lacing_vals[++ptr];
+ size=val&0xff;
+ if((val&0x200)!=0)op.e_o_s=0x200;
+ bytes+=size;
+ }
+
+ op.packetno=packetno;
+ op.granulepos=granule_vals[ptr];
+ op.bytes=bytes;
+
+//System.out.println(this+" # body_returned="+body_returned);
+ body_returned+=bytes;
+//System.out.println(this+"## body_returned="+body_returned);
+
+ lacing_returned=ptr+1;
+ }
+ packetno++;
+ return(1);
+ }
+
+
+ // add the incoming page to the stream state; we decompose the page
+ // into packet segments here as well.
+
+ public int pagein(Page og){
+ byte[] header_base=og.header_base;
+ int header=og.header;
+ byte[] body_base=og.body_base;
+ int body=og.body;
+ int bodysize=og.body_len;
+ int segptr=0;
+
+ int version=og.version();
+ int continued=og.continued();
+ int bos=og.bos();
+ int eos=og.eos();
+ long granulepos=og.granulepos();
+ int _serialno=og.serialno();
+ int _pageno=og.pageno();
+ int segments=header_base[header+26]&0xff;
+
+ // clean up 'returned data'
+ {
+ int lr=lacing_returned;
+ int br=body_returned;
+
+ // body data
+
+//System.out.println("br="+br+", body_fill="+body_fill);
+
+ if(br!=0){
+ body_fill-=br;
+ if(body_fill!=0){
+ System.arraycopy(body_data, br, body_data, 0, body_fill);
+ }
+ body_returned=0;
+ }
+
+//System.out.println("?? br="+br+", body_fill="+body_fill+" body_returned="+body_returned);
+
+ if(lr!=0){
+ // segment table
+ if((lacing_fill-lr)!=0){
+ System.arraycopy(lacing_vals, lr, lacing_vals, 0, lacing_fill-lr);
+ System.arraycopy(granule_vals, lr, granule_vals, 0, lacing_fill-lr);
+ }
+ lacing_fill-=lr;
+ lacing_packet-=lr;
+ lacing_returned=0;
+ }
+ }
+
+ // check the serial number
+ if(_serialno!=serialno)return(-1);
+ if(version>0)return(-1);
+
+ lacing_expand(segments+1);
+
+ // are we in sequence?
+ if(_pageno!=pageno){
+ int i;
+
+ // unroll previous partial packet (if any)
+ for(i=lacing_packet;i<lacing_fill;i++){
+ body_fill-=lacing_vals[i]&0xff;
+//System.out.println("??");
+ }
+ lacing_fill=lacing_packet;
+
+ // make a note of dropped data in segment table
+ if(pageno!=-1){
+ lacing_vals[lacing_fill++]=0x400;
+ lacing_packet++;
+ }
+
+ // are we a 'continued packet' page? If so, we'll need to skip
+ // some segments
+ if(continued!=0){
+ bos=0;
+ for(;segptr<segments;segptr++){
+ int val=(header_base[header+27+segptr]&0xff);
+ body+=val;
+ bodysize-=val;
+ if(val<255){
+ segptr++;
+ break;
+ }
+ }
+ }
+ }
+
+//System.out.println("bodysize="+bodysize);
+
+ if(bodysize!=0){
+ body_expand(bodysize);
+ System.arraycopy(body_base, body, body_data, body_fill, bodysize);
+ body_fill+=bodysize;
+ }
+
+//System.out.println("bodyfill="+body_fill);
+
+ {
+ int saved=-1;
+ while(segptr<segments){
+ int val=(header_base[header+27+segptr]&0xff);
+ lacing_vals[lacing_fill]=val;
+ granule_vals[lacing_fill]=-1;
+
+ if(bos!=0){
+ lacing_vals[lacing_fill]|=0x100;
+ bos=0;
+ }
+
+ if(val<255)saved=lacing_fill;
+
+ lacing_fill++;
+ segptr++;
+
+ if(val<255)lacing_packet=lacing_fill;
+ }
+
+ /* set the granulepos on the last pcmval of the last full packet */
+ if(saved!=-1){
+ granule_vals[saved]=granulepos;
+ }
+ }
+
+ if(eos!=0){
+ e_o_s=1;
+ if(lacing_fill>0)
+ lacing_vals[lacing_fill-1]|=0x200;
+ }
+
+ pageno=_pageno+1;
+ return(0);
+ }
+
+
+/* This will flush remaining packets into a page (returning nonzero),
+ even if there is not enough data to trigger a flush normally
+ (undersized page). If there are no packets or partial packets to
+ flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will
+ try to flush a normal sized page like ogg_stream_pageout; a call to
+ ogg_stream_flush does not gurantee that all packets have flushed.
+ Only a return value of 0 from ogg_stream_flush indicates all packet
+ data is flushed into pages.
+
+ ogg_stream_page will flush the last page in a stream even if it's
+ undersized; you almost certainly want to use ogg_stream_pageout
+ (and *not* ogg_stream_flush) unless you need to flush an undersized
+ page in the middle of a stream for some reason. */
+
+ public int flush(Page og){
+
+//System.out.println(this+" ---body_returned: "+body_returned);
+
+ int i;
+ int vals=0;
+ int maxvals=(lacing_fill>255?255:lacing_fill);
+ int bytes=0;
+ int acc=0;
+ long granule_pos=granule_vals[0];
+
+ if(maxvals==0)return(0);
+
+ /* construct a page */
+ /* decide how many segments to include */
+
+ /* If this is the initial header case, the first page must only include
+ the initial header packet */
+ if(b_o_s==0){ /* 'initial header page' case */
+ granule_pos=0;
+ for(vals=0;vals<maxvals;vals++){
+ if((lacing_vals[vals]&0x0ff)<255){
+ vals++;
+ break;
+ }
+ }
+ }
+ else{
+ for(vals=0;vals<maxvals;vals++){
+ if(acc>4096)break;
+ acc+=(lacing_vals[vals]&0x0ff);
+ granule_pos=granule_vals[vals];
+ }
+ }
+
+ /* construct the header in temp storage */
+ System.arraycopy("OggS".getBytes(), 0, header, 0, 4);
+
+ /* stream structure version */
+ header[4]=0x00;
+
+ /* continued packet flag? */
+ header[5]=0x00;
+ if((lacing_vals[0]&0x100)==0)header[5]|=0x01;
+ /* first page flag? */
+ if(b_o_s==0) header[5]|=0x02;
+ /* last page flag? */
+ if(e_o_s!=0 && lacing_fill==vals) header[5]|=0x04;
+ b_o_s=1;
+
+ /* 64 bits of PCM position */
+ for(i=6;i<14;i++){
+ header[i]=(byte)granule_pos;
+ granule_pos>>>=8;
+ }
+
+ /* 32 bits of stream serial number */
+ {
+ int _serialno=serialno;
+ for(i=14;i<18;i++){
+ header[i]=(byte)_serialno;
+ _serialno>>>=8;
+ }
+ }
+
+ /* 32 bits of page counter (we have both counter and page header
+ because this val can roll over) */
+ if(pageno==-1)pageno=0; /* because someone called
+ stream_reset; this would be a
+ strange thing to do in an
+ encode stream, but it has
+ plausible uses */
+ {
+ int _pageno=pageno++;
+ for(i=18;i<22;i++){
+ header[i]=(byte)_pageno;
+ _pageno>>>=8;
+ }
+ }
+
+ /* zero for computation; filled in later */
+ header[22]=0;
+ header[23]=0;
+ header[24]=0;
+ header[25]=0;
+
+ /* segment table */
+ header[26]=(byte)vals;
+ for(i=0;i<vals;i++){
+ header[i+27]=(byte)lacing_vals[i];
+ bytes+=(header[i+27]&0xff);
+ }
+
+ /* set pointers in the ogg_page struct */
+ og.header_base=header;
+ og.header=0;
+ og.header_len=header_fill=vals+27;
+ og.body_base=body_data;
+ og.body=body_returned;
+ og.body_len=bytes;
+
+ /* advance the lacing data and set the body_returned pointer */
+
+//System.out.println("###body_returned: "+body_returned);
+
+ lacing_fill-=vals;
+ System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill*4);
+ System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill*8);
+ body_returned+=bytes;
+
+//System.out.println("####body_returned: "+body_returned);
+
+ /* calculate the checksum */
+
+ og.checksum();
+
+ /* done */
+ return(1);
+ }
+
+
+/* This constructs pages from buffered packet segments. The pointers
+returned are to static buffers; do not free. The returned buffers are
+good only until the next call (using the same ogg_stream_state) */
+ public int pageout(Page og){
+// if(body_returned!=0){
+// /* advance packet data according to the body_returned pointer. We
+// had to keep it around to return a pointer into the buffer last
+// call */
+//
+// body_fill-=body_returned;
+// if(body_fill!=0){ // overlap?
+// System.arraycopy(body_data, body_returned, body_data, 0, body_fill);
+// }
+// body_returned=0;
+// }
+//
+//System.out.println("pageout: e_o_s="+e_o_s+" lacing_fill="+lacing_fill+" body_fill="+body_fill+", lacing_fill="+lacing_fill+" b_o_s="+b_o_s);
+//
+// if((e_o_s!=0&&lacing_fill!=0) || /* 'were done, now flush' case */
+// body_fill > 4096 || /* 'page nominal size' case */
+// lacing_fill>=255 || /* 'segment table full' case */
+// (lacing_fill!=0&&b_o_s==0)){ /* 'initial header page' case */
+// int vals=0,bytes=0;
+// int maxvals=(lacing_fill>255?255:lacing_fill);
+// long acc=0;
+// long pcm_pos=granule_vals[0];
+//
+// /* construct a page */
+// /* decide how many segments to include */
+//
+// /* If this is the initial header case, the first page must only include
+// the initial header packet */
+// if(b_o_s==0){ /* 'initial header page' case */
+// pcm_pos=0;
+// for(vals=0;vals<maxvals;vals++){
+// if((lacing_vals[vals]&0x0ff)<255){
+// vals++;
+// break;
+// }
+// }
+// }
+// else{
+// for(vals=0;vals<maxvals;vals++){
+// if(acc>4096)break;
+// acc+=lacing_vals[vals]&0x0ff;
+// pcm_pos=granule_vals[vals];
+// }
+// }
+//
+// /* construct the header in temp storage */
+// System.arraycopy("OggS".getBytes(), 0, header, 0, 4);
+//
+// /* stream structure version */
+// header[4]=0x00;
+//
+// /* continued packet flag? */
+// header[5]=0x00;
+// if((lacing_vals[0]&0x100)==0)header[5]|=0x01;
+// /* first page flag? */
+// if(b_o_s==0)header[5]|=0x02;
+// /* last page flag? */
+// if(e_o_s!=0 && lacing_fill==vals)header[5]|=0x04;
+// b_o_s=1;
+//
+// /* 64 bits of PCM position */
+// for(int i=6;i<14;i++){
+// header[i]=(byte)pcm_pos;
+// pcm_pos>>>=8;
+// }
+//
+// /* 32 bits of stream serial number */
+// {
+// int serialn=serialno;
+// for(int i=14;i<18;i++){
+// header[i]=(byte)serialn;
+// serialn>>>=8;
+// }
+// }
+//
+//
+///* 32 bits of page counter (we have both counter and page header
+// because this val can roll over) */
+// if(pageno==-1)pageno=0; /* because someone called
+// stream_reset; this would be a
+// strange thing to do in an
+// encode stream, but it has
+// plausible uses */
+// {
+// int pagen=pageno++;
+// for(int i=18;i<22;i++){
+// header[i]=(byte)pagen;
+// pagen>>>=8;
+// }
+// }
+//
+// /* zero for computation; filled in later */
+// header[22]=0;
+// header[23]=0;
+// header[24]=0;
+// header[25]=0;
+//
+// /* segment table */
+// header[26]=(byte)vals;
+// for(int i=0;i<vals;i++){
+// header[i+27]=(byte)lacing_vals[i];
+// bytes+=header[i+27]&0xff;
+//// bytes+=header[i+27]=(lacing_vals[i]&0xff);
+// }
+//
+// /* advance the lacing data and set the body_returned pointer */
+//
+// lacing_fill-=vals;
+// System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill);
+// System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill);
+// body_returned=bytes;
+//
+// /* set pointers in the ogg_page struct */
+// og.header_base=header;
+// og.header=0;
+// og.header_len=header_fill=vals+27;
+//
+// og.body_base=body_data;
+// og.body=0;
+// og.body_len=bytes;
+//
+// /* calculate the checksum */
+//
+// og.checksum();
+// return(1);
+// }
+// /* not enough data to construct a page and not end of stream */
+// return(0);
+//System.out.println("pageout: "+body_returned);
+ if((e_o_s!=0&&lacing_fill!=0) || /* 'were done, now flush' case */
+ body_fill-body_returned> 4096 || /* 'page nominal size' case */
+ lacing_fill>=255 || /* 'segment table full' case */
+ (lacing_fill!=0&&b_o_s==0)){ /* 'initial header page' case */
+ return flush(og);
+ }
+ return 0;
+ }
+
+ public int eof(){
+ return e_o_s;
+ }
+
+ public int reset(){
+ body_fill=0;
+ body_returned=0;
+
+ lacing_fill=0;
+ lacing_packet=0;
+ lacing_returned=0;
+
+ header_fill=0;
+
+ e_o_s=0;
+ b_o_s=0;
+ pageno=-1;
+ packetno=0;
+ granulepos=0;
+ return(0);
+ }
+}
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+// DECODING PRIMITIVES: packet streaming layer
+
+// This has two layers to place more of the multi-serialno and paging
+// control in the application's hands. First, we expose a data buffer
+// using ogg_decode_buffer(). The app either copies into the
+// buffer, or passes it directly to read(), etc. We then call
+// ogg_decode_wrote() to tell how many bytes we just added.
+//
+// Pages are returned (pointers into the buffer in ogg_sync_state)
+// by ogg_decode_stream(). The page is then submitted to
+// ogg_decode_page() along with the appropriate
+// ogg_stream_state* (ie, matching serialno). We then get raw
+// packets out calling ogg_stream_packet() with a
+// ogg_stream_state. See the 'frame-prog.txt' docs for details and
+// example code.
+
+public class SyncState{
+
+ public byte[] data;
+ int storage;
+ int fill;
+ int returned;
+
+ int unsynced;
+ int headerbytes;
+ int bodybytes;
+
+ public int clear(){
+ data=null;
+ return(0);
+ }
+
+// !!!!!!!!!!!!
+// byte[] buffer(int size){
+ public int buffer(int size){
+ // first, clear out any space that has been previously returned
+ if(returned!=0){
+ fill-=returned;
+ if(fill>0){
+ System.arraycopy(data, returned, data, 0, fill);
+ }
+ returned=0;
+ }
+
+ if(size>storage-fill){
+ // We need to extend the internal buffer
+ int newsize=size+fill+4096; // an extra page to be nice
+ if(data!=null){
+ byte[] foo=new byte[newsize];
+ System.arraycopy(data, 0, foo, 0, data.length);
+ data=foo;
+ }
+ else{
+ data=new byte[newsize];
+ }
+ storage=newsize;
+ }
+
+ // expose a segment at least as large as requested at the fill mark
+// return((char *)oy->data+oy->fill);
+// return(data);
+ return(fill);
+ }
+
+ public int wrote(int bytes){
+ if(fill+bytes>storage)return(-1);
+ fill+=bytes;
+ return(0);
+ }
+
+// sync the stream. This is meant to be useful for finding page
+// boundaries.
+//
+// return values for this:
+// -n) skipped n bytes
+// 0) page not ready; more data (no bytes skipped)
+// n) page synced at current location; page length n bytes
+ private Page pageseek=new Page();
+ private byte[] chksum=new byte[4];
+ public int pageseek(Page og){
+ int page=returned;
+ int next;
+ int bytes=fill-returned;
+
+ if(headerbytes==0){
+ int _headerbytes,i;
+ if(bytes<27)return(0); // not enough for a header
+
+ /* verify capture pattern */
+//!!!!!!!!!!!
+ if(data[page]!='O' ||
+ data[page+1]!='g' ||
+ data[page+2]!='g' ||
+ data[page+3]!='S'){
+ headerbytes=0;
+ bodybytes=0;
+
+ // search for possible capture
+ next=0;
+ for(int ii=0; ii<bytes-1; ii++){
+ if(data[page+1+ii]=='O'){next=page+1+ii; break;}
+ }
+ //next=memchr(page+1,'O',bytes-1);
+ if(next==0) next=fill;
+
+ returned=next;
+ return(-(next-page));
+ }
+ _headerbytes=(data[page+26]&0xff)+27;
+ if(bytes<_headerbytes)return(0); // not enough for header + seg table
+
+ // count up body length in the segment table
+
+ for(i=0;i<(data[page+26]&0xff);i++){
+ bodybytes+=(data[page+27+i]&0xff);
+ }
+ headerbytes=_headerbytes;
+ }
+
+ if(bodybytes+headerbytes>bytes)return(0);
+
+ // The whole test page is buffered. Verify the checksum
+ synchronized(chksum){
+ // Grab the checksum bytes, set the header field to zero
+
+ System.arraycopy(data, page+22, chksum, 0, 4);
+ data[page+22]=0;
+ data[page+23]=0;
+ data[page+24]=0;
+ data[page+25]=0;
+
+ // set up a temp page struct and recompute the checksum
+ Page log=pageseek;
+ log.header_base=data;
+ log.header=page;
+ log.header_len=headerbytes;
+
+ log.body_base=data;
+ log.body=page+headerbytes;
+ log.body_len=bodybytes;
+ log.checksum();
+
+ // Compare
+ if(chksum[0]!=data[page+22] ||
+ chksum[1]!=data[page+23] ||
+ chksum[2]!=data[page+24] ||
+ chksum[3]!=data[page+25]){
+ // D'oh. Mismatch! Corrupt page (or miscapture and not a page at all)
+ // replace the computed checksum with the one actually read in
+ System.arraycopy(chksum, 0, data, page+22, 4);
+ // Bad checksum. Lose sync */
+
+ headerbytes=0;
+ bodybytes=0;
+ // search for possible capture
+ next=0;
+ for(int ii=0; ii<bytes-1; ii++){
+ if(data[page+1+ii]=='O'){next=page+1+ii; break;}
+ }
+ //next=memchr(page+1,'O',bytes-1);
+ if(next==0) next=fill;
+ returned=next;
+ return(-(next-page));
+ }
+ }
+
+ // yes, have a whole page all ready to go
+ {
+ page=returned;
+
+ if(og!=null){
+ og.header_base=data;
+ og.header=page;
+ og.header_len=headerbytes;
+ og.body_base=data;
+ og.body=page+headerbytes;
+ og.body_len=bodybytes;
+ }
+
+ unsynced=0;
+ returned+=(bytes=headerbytes+bodybytes);
+ headerbytes=0;
+ bodybytes=0;
+ return(bytes);
+ }
+// headerbytes=0;
+// bodybytes=0;
+// next=0;
+// for(int ii=0; ii<bytes-1; ii++){
+// if(data[page+1+ii]=='O'){next=page+1+ii;}
+// }
+// //next=memchr(page+1,'O',bytes-1);
+// if(next==0) next=fill;
+// returned=next;
+// return(-(next-page));
+ }
+
+
+// sync the stream and get a page. Keep trying until we find a page.
+// Supress 'sync errors' after reporting the first.
+//
+// return values:
+// -1) recapture (hole in data)
+// 0) need more data
+// 1) page returned
+//
+// Returns pointers into buffered data; invalidated by next call to
+// _stream, _clear, _init, or _buffer
+
+ public int pageout(Page og){
+ // all we need to do is verify a page at the head of the stream
+ // buffer. If it doesn't verify, we look for the next potential
+ // frame
+
+ while(true){
+ int ret=pageseek(og);
+ if(ret>0){
+ // have a page
+ return(1);
+ }
+ if(ret==0){
+ // need more data
+ return(0);
+ }
+
+ // head did not start a synced page... skipped some bytes
+ if(unsynced==0){
+ unsynced=1;
+ return(-1);
+ }
+ // loop. keep looking
+ }
+ }
+
+// clear things to an initial state. Good to call, eg, before seeking
+ public int reset(){
+ fill=0;
+ returned=0;
+ unsynced=0;
+ headerbytes=0;
+ bodybytes=0;
+ return(0);
+ }
+ public void init(){}
+
+ public int getDataOffset(){ return returned; }
+ public int getBufferOffset(){ return fill; }
+}
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+class AllocChain{
+ Object ptr;
+ AllocChain next;
+};
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+public class Block{
+ ///necessary stream state for linking to the framing abstraction
+ float[][] pcm=new float[0][]; // this is a pointer into local storage
+ Buffer opb=new Buffer();
+
+ int lW;
+ int W;
+ int nW;
+ int pcmend;
+ int mode;
+
+ int eofflag;
+ long granulepos;
+ long sequence;
+ DspState vd; // For read-only access of configuration
+
+ // local storage to avoid remallocing; it's up to the mapping to
+ // structure it
+//byte[] localstore;
+//int localtop;
+//int localalloc;
+//int totaluse;
+//AllocChain reap;
+
+ // bitmetrics for the frame
+ int glue_bits;
+ int time_bits;
+ int floor_bits;
+ int res_bits;
+
+ public Block(DspState vd){
+ this.vd=vd;
+// localalloc=0;
+// localstore=null;
+ if(vd.analysisp!=0){
+ opb.writeinit();
+ }
+ }
+
+ public void init(DspState vd){
+ this.vd=vd;
+ }
+
+// int alloc(int bytes){
+// bytes=(bytes+(8-1))&(~(8-1));
+// if(bytes+localtop>localalloc){
+// if(localstore!=null){
+// AllocChain link=new AllocChain();
+// totaluse+=localtop;
+// link.next=reap;
+// link.ptr=localstore;
+// reap=link;
+// }
+// // highly conservative
+// localalloc=bytes;
+// localstore=new byte[localalloc];
+// localtop=0;
+// }
+// {
+// int foo=localtop;
+// //void *ret=(void *)(((char *)vb->localstore)+vb->localtop);
+// localtop+=bytes;
+// return foo;
+// }
+// }
+
+ // reap the chain, pull the ripcord
+// void ripcord(){
+// // reap the chain
+// while(reap!=null){
+// AllocChain next=reap.next;
+// //free(reap->ptr);
+// reap.ptr=null;
+// //memset(reap,0,sizeof(struct alloc_chain));
+// //free(reap);
+// reap=next;
+// }
+// // consolidate storage
+// if(totaluse!=0){
+// //vb->localstore=realloc(vb->localstore,vb->totaluse+vb->localalloc);
+// byte[] foo=new byte[totaluse+localalloc];
+// System.arraycopy(localstore, 0, foo, 0, localstore.length);
+// localstore=foo;
+// localalloc+=totaluse;
+// totaluse=0;
+// }
+// // pull the ripcord
+// localtop=0;
+// reap=null;
+// }
+
+ public int clear(){
+ if(vd!=null){
+ if(vd.analysisp!=0){
+ opb.writeclear();
+ }
+ }
+ //ripcord();
+ //if(localstore!=null)
+ // localstore=null;
+ //memset(vb,0,sizeof(vorbis_block));
+ return(0);
+ }
+
+ public int synthesis(Packet op){
+ Info vi=vd.vi;
+
+ // first things first. Make sure decode is ready
+ // ripcord();
+ opb.readinit(op.packet_base, op.packet, op.bytes);
+
+ // Check the packet type
+ if(opb.read(1)!=0){
+ // Oops. This is not an audio data packet
+ return(-1);
+ }
+
+ // read our mode and pre/post windowsize
+ int _mode=opb.read(vd.modebits);
+ if(_mode==-1)return(-1);
+
+ mode=_mode;
+ W=vi.mode_param[mode].blockflag;
+ if(W!=0){
+ lW=opb.read(1);
+ nW=opb.read(1);
+ if(nW==-1) return(-1);
+ }
+ else{
+ lW=0;
+ nW=0;
+ }
+
+ // more setup
+ granulepos=op.granulepos;
+ sequence=op.packetno-3; // first block is third packet
+ eofflag=op.e_o_s;
+
+ // alloc pcm passback storage
+ pcmend=vi.blocksizes[W];
+ //pcm=alloc(vi.channels);
+ if(pcm.length<vi.channels){
+ pcm=new float[vi.channels][];
+ }
+ for(int i=0;i<vi.channels;i++){
+ if(pcm[i]==null || pcm[i].length<pcmend){
+ pcm[i]=new float[pcmend];
+ //pcm[i]=alloc(pcmend);
+ }
+ else{
+ for(int j=0;j<pcmend;j++){ pcm[i][j]=0; }
+ }
+ }
+
+ // unpack_header enforces range checking
+ int type=vi.map_type[vi.mode_param[mode].mapping];
+ return(FuncMapping.mapping_P[type].inverse(this, vd.mode[mode]));
+ }
+}
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+class ChainingExample{
+ public static void main(String[] arg){
+ VorbisFile ov=null;
+
+ try{
+ ov=new VorbisFile(System.in, null, -1);
+ }
+ catch(Exception e){
+ System.err.println(e);
+ return;
+ }
+
+ if(ov.seekable()){
+ System.out.println("Input bitstream contained "+ov.streams()+" logical bitstream section(s).");
+ System.out.println("Total bitstream playing time: "+ov.time_total(-1)+" seconds\n");
+ }
+ else{
+ System.out.println("Standard input was not seekable.");
+ System.out.println("First logical bitstream information:\n");
+ }
+
+ for(int i=0;i<ov.streams();i++){
+ Info vi=ov.getInfo(i);
+ System.out.println("\tlogical bitstream section "+(i+1)+" information:");
+ System.out.println("\t\t"+vi.rate+"Hz "+vi.channels+" channels bitrate "+
+ (ov.bitrate(i)/1000)+"kbps serial number="+ov.serialnumber(i));
+ System.out.print("\t\tcompressed length: "+ov.raw_total(i)+" bytes ");
+ System.out.println(" play time: "+ov.time_total(i)+"s");
+ Comment vc=ov.getComment(i);
+ System.out.println(vc);
+ }
+ //clear(&ov);
+ }
+}
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+class CodeBook{
+ int dim; // codebook dimensions (elements per vector)
+ int entries; // codebook entries
+ StaticCodeBook c=new StaticCodeBook();
+
+ float[] valuelist; // list of dim*entries actual entry values
+ int[] codelist; // list of bitstream codewords for each entry
+ DecodeAux decode_tree;
+
+ // returns the number of bits
+ int encode(int a, Buffer b){
+ b.write(codelist[a], c.lengthlist[a]);
+ return(c.lengthlist[a]);
+ }
+
+ // One the encode side, our vector writers are each designed for a
+ // specific purpose, and the encoder is not flexible without modification:
+ //
+ // The LSP vector coder uses a single stage nearest-match with no
+ // interleave, so no step and no error return. This is specced by floor0
+ // and doesn't change.
+ //
+ // Residue0 encoding interleaves, uses multiple stages, and each stage
+ // peels of a specific amount of resolution from a lattice (thus we want
+ // to match by threshhold, not nearest match). Residue doesn't *have* to
+ // be encoded that way, but to change it, one will need to add more
+ // infrastructure on the encode side (decode side is specced and simpler)
+
+ // floor0 LSP (single stage, non interleaved, nearest match)
+ // returns entry number and *modifies a* to the quantization value
+ int errorv(float[] a){
+ int best=best(a,1);
+ for(int k=0;k<dim;k++){
+ a[k]=valuelist[best*dim+k];
+ }
+ return(best);
+ }
+
+ // returns the number of bits and *modifies a* to the quantization value
+ int encodev(int best, float[] a, Buffer b){
+ for(int k=0;k<dim;k++){
+ a[k]=valuelist[best*dim+k];
+ }
+ return(encode(best,b));
+ }
+
+ // res0 (multistage, interleave, lattice)
+ // returns the number of bits and *modifies a* to the remainder value
+ int encodevs(float[] a, Buffer b, int step,int addmul){
+ int best=besterror(a,step,addmul);
+ return(encode(best,b));
+ }
+
+ private int[] t=new int[15]; // decodevs_add is synchronized for re-using t.
+ synchronized int decodevs_add(float[]a, int offset, Buffer b, int n){
+ int step=n/dim;
+ int entry;
+ int i,j,o;
+
+ if(t.length<step){
+ t=new int[step];
+ }
+
+ for(i = 0; i < step; i++){
+ entry=decode(b);
+ if(entry==-1)return(-1);
+ t[i]=entry*dim;
+ }
+ for(i=0,o=0;i<dim;i++,o+=step){
+ for(j=0;j<step;j++){
+ a[offset+o+j]+=valuelist[t[j]+i];
+ }
+ }
+
+ return(0);
+ }
+
+ int decodev_add(float[]a, int offset, Buffer b,int n){
+ int i,j,entry;
+ int t;
+
+ if(dim>8){
+ for(i=0;i<n;){
+ entry = decode(b);
+ if(entry==-1)return(-1);
+ t=entry*dim;
+ for(j=0;j<dim;){
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ }
+ }
+ }
+ else{
+ for(i=0;i<n;){
+ entry=decode(b);
+ if(entry==-1)return(-1);
+ t=entry*dim;
+ j=0;
+ switch(dim){
+ case 8:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 7:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 6:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 5:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 4:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 3:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 2:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 1:
+ a[offset+(i++)]+=valuelist[t+(j++)];
+ case 0:
+ break;
+ }
+ }
+ }
+ return(0);
+ }
+
+ int decodev_set(float[] a,int offset, Buffer b, int n){
+ int i,j,entry;
+ int t;
+
+ for(i=0;i<n;){
+ entry = decode(b);
+ if(entry==-1)return(-1);
+ t=entry*dim;
+ for(j=0;j<dim;){
+ a[offset+i++]=valuelist[t+(j++)];
+ }
+ }
+ return(0);
+ }
+
+ int decodevv_add(float[][] a, int offset,int ch, Buffer b,int n){
+ int i,j,k,entry;
+ int chptr=0;
+ //System.out.println("decodevv_add: a="+a+",b="+b+",valuelist="+valuelist);
+
+ for(i=offset/ch;i<(offset+n)/ch;){
+ entry = decode(b);
+ if(entry==-1)return(-1);
+
+ int t = entry*dim;
+ for(j=0;j<dim;j++){
+ a[chptr++][i]+=valuelist[t+j];
+ if(chptr==ch){
+ chptr=0;
+ i++;
+ }
+ }
+ }
+ return(0);
+ }
+
+
+ // Decode side is specced and easier, because we don't need to find
+ // matches using different criteria; we simply read and map. There are
+ // two things we need to do 'depending':
+ //
+ // We may need to support interleave. We don't really, but it's
+ // convenient to do it here rather than rebuild the vector later.
+ //
+ // Cascades may be additive or multiplicitive; this is not inherent in
+ // the codebook, but set in the code using the codebook. Like
+ // interleaving, it's easiest to do it here.
+ // stage==0 -> declarative (set the value)
+ // stage==1 -> additive
+ // stage==2 -> multiplicitive
+
+ // returns the entry number or -1 on eof
+ int decode(Buffer b){
+ int ptr=0;
+ DecodeAux t=decode_tree;
+ int lok=b.look(t.tabn);
+ //System.err.println(this+" "+t+" lok="+lok+", tabn="+t.tabn);
+
+ if(lok>=0){
+ ptr=t.tab[lok];
+ b.adv(t.tabl[lok]);
+ if(ptr<=0){
+ return -ptr;
+ }
+ }
+ do{
+ switch(b.read1()){
+ case 0:
+ ptr=t.ptr0[ptr];
+ break;
+ case 1:
+ ptr=t.ptr1[ptr];
+ break;
+ case -1:
+ default:
+ return(-1);
+ }
+ }
+ while(ptr>0);
+ return(-ptr);
+ }
+
+ // returns the entry number or -1 on eof
+ int decodevs(float[] a, int index, Buffer b, int step,int addmul){
+ int entry=decode(b);
+ if(entry==-1)return(-1);
+ switch(addmul){
+ case -1:
+ for(int i=0,o=0;i<dim;i++,o+=step)
+ a[index+o]=valuelist[entry*dim+i];
+ break;
+ case 0:
+ for(int i=0,o=0;i<dim;i++,o+=step)
+ a[index+o]+=valuelist[entry*dim+i];
+ break;
+ case 1:
+ for(int i=0,o=0;i<dim;i++,o+=step)
+ a[index+o]*=valuelist[entry*dim+i];
+ break;
+ default:
+ //System.err.println("CodeBook.decodeves: addmul="+addmul);
+ }
+ return(entry);
+ }
+
+ int best(float[] a, int step){
+ EncodeAuxNearestMatch nt=c.nearest_tree;
+ EncodeAuxThreshMatch tt=c.thresh_tree;
+ int ptr=0;
+
+ // we assume for now that a thresh tree is the only other possibility
+ if(tt!=null){
+ int index=0;
+ // find the quant val of each scalar
+ for(int k=0,o=step*(dim-1);k<dim;k++,o-=step){
+ int i;
+ // linear search the quant list for now; it's small and although
+ // with > 8 entries, it would be faster to bisect, this would be
+ // a misplaced optimization for now
+ for(i=0;i<tt.threshvals-1;i++){
+ if(a[o]<tt.quantthresh[i]){
+ break;
+ }
+ }
+ index=(index*tt.quantvals)+tt.quantmap[i];
+ }
+ // regular lattices are easy :-)
+ if(c.lengthlist[index]>0){
+ // is this unused? If so, we'll
+ // use a decision tree after all
+ // and fall through
+ return(index);
+ }
+ }
+ if(nt!=null){
+ // optimized using the decision tree
+ while(true){
+ float c=0.f;
+ int p=nt.p[ptr];
+ int q=nt.q[ptr];
+ for(int k=0,o=0;k<dim;k++,o+=step){
+ c+=(valuelist[p+k]-valuelist[q+k])*
+ (a[o]-(valuelist[p+k]+valuelist[q+k])*.5);
+ }
+ if(c>0.){ // in A
+ ptr= -nt.ptr0[ptr];
+ }
+ else{ // in B
+ ptr= -nt.ptr1[ptr];
+ }
+ if(ptr<=0)break;
+ }
+ return(-ptr);
+ }
+
+ // brute force it!
+ {
+ int besti=-1;
+ float best=0.f;
+ int e=0;
+ for(int i=0;i<entries;i++){
+ if(c.lengthlist[i]>0){
+ float _this=dist(dim, valuelist, e, a, step);
+ if(besti==-1 || _this<best){
+ best=_this;
+ besti=i;
+ }
+ }
+ e+=dim;
+ }
+ return(besti);
+ }
+ }
+
+ // returns the entry number and *modifies a* to the remainder value
+ int besterror(float[] a, int step, int addmul){
+ int best=best(a,step);
+ switch(addmul){
+ case 0:
+ for(int i=0,o=0;i<dim;i++,o+=step)
+ a[o]-=valuelist[best*dim+i];
+ break;
+ case 1:
+ for(int i=0,o=0;i<dim;i++,o+=step){
+ float val=valuelist[best*dim+i];
+ if(val==0){
+ a[o]=0;
+ }else{
+ a[o]/=val;
+ }
+ }
+ break;
+ }
+ return(best);
+ }
+
+ void clear(){
+ // static book is not cleared; we're likely called on the lookup and
+ // the static codebook belongs to the info struct
+ //if(decode_tree!=null){
+ // free(b->decode_tree->ptr0);
+ // free(b->decode_tree->ptr1);
+ // memset(b->decode_tree,0,sizeof(decode_aux));
+ // free(b->decode_tree);
+ //}
+ //if(valuelist!=null)free(b->valuelist);
+ //if(codelist!=null)free(b->codelist);
+ //memset(b,0,sizeof(codebook));
+ }
+
+ private static float dist(int el, float[] ref, int index, float[] b, int step){
+ float acc=(float)0.;
+ for(int i=0; i<el; i++){
+ float val=(ref[index+i]-b[i*step]);
+ acc+=val*val;
+ }
+ return(acc);
+ }
+
+/*
+ int init_encode(StaticCodeBook s){
+ //memset(c,0,sizeof(codebook));
+ c=s;
+ entries=s.entries;
+ dim=s.dim;
+ codelist=make_words(s.lengthlist, s.entries);
+ valuelist=s.unquantize();
+ return(0);
+ }
+*/
+
+ int init_decode(StaticCodeBook s){
+ //memset(c,0,sizeof(codebook));
+ c=s;
+ entries=s.entries;
+ dim=s.dim;
+ valuelist=s.unquantize();
+
+ decode_tree=make_decode_tree();
+ if(decode_tree==null){
+ //goto err_out;
+ clear();
+ return(-1);
+ }
+ return(0);
+// err_out:
+// vorbis_book_clear(c);
+// return(-1);
+ }
+
+ // given a list of word lengths, generate a list of codewords. Works
+ // for length ordered or unordered, always assigns the lowest valued
+ // codewords first. Extended to handle unused entries (length 0)
+ static int[] make_words(int[] l, int n){
+ int[] marker=new int[33];
+ int[] r=new int[n];
+ //memset(marker,0,sizeof(marker));
+
+ for(int i=0;i<n;i++){
+ int length=l[i];
+ if(length>0){
+ int entry=marker[length];
+
+ // when we claim a node for an entry, we also claim the nodes
+ // below it (pruning off the imagined tree that may have dangled
+ // from it) as well as blocking the use of any nodes directly
+ // above for leaves
+
+ // update ourself
+ if(length<32 && (entry>>>length)!=0){
+ // error condition; the lengths must specify an overpopulated tree
+ //free(r);
+ return(null);
+ }
+ r[i]=entry;
+
+ // Look to see if the next shorter marker points to the node
+ // above. if so, update it and repeat.
+ {
+ for(int j=length;j>0;j--){
+ if((marker[j]&1)!=0){
+ // have to jump branches
+ if(j==1)marker[1]++;
+ else marker[j]=marker[j-1]<<1;
+ break; // invariant says next upper marker would already
+ // have been moved if it was on the same path
+ }
+ marker[j]++;
+ }
+ }
+
+ // prune the tree; the implicit invariant says all the longer
+ // markers were dangling from our just-taken node. Dangle them
+ // from our *new* node.
+ for(int j=length+1;j<33;j++){
+ if((marker[j]>>>1) == entry){
+ entry=marker[j];
+ marker[j]=marker[j-1]<<1;
+ }
+ else{
+ break;
+ }
+ }
+ }
+ }
+
+ // bitreverse the words because our bitwise packer/unpacker is LSb
+ // endian
+ for(int i=0;i<n;i++){
+ int temp=0;
+ for(int j=0;j<l[i];j++){
+ temp<<=1;
+ temp|=(r[i]>>>j)&1;
+ }
+ r[i]=temp;
+ }
+
+ return(r);
+ }
+
+ // build the decode helper tree from the codewords
+ DecodeAux make_decode_tree(){
+ int top=0;
+ DecodeAux t=new DecodeAux();
+ int[] ptr0=t.ptr0=new int[entries*2];
+ int[] ptr1=t.ptr1=new int[entries*2];
+ int[] codelist=make_words(c.lengthlist, c.entries);
+
+ if(codelist==null)return(null);
+ t.aux=entries*2;
+
+ for(int i=0;i<entries;i++){
+ if(c.lengthlist[i]>0){
+ int ptr=0;
+ int j;
+ for(j=0;j<c.lengthlist[i]-1;j++){
+ int bit=(codelist[i]>>>j)&1;
+ if(bit==0){
+ if(ptr0[ptr]==0){
+ ptr0[ptr]=++top;
+ }
+ ptr=ptr0[ptr];
+ }
+ else{
+ if(ptr1[ptr]==0){
+ ptr1[ptr]= ++top;
+ }
+ ptr=ptr1[ptr];
+ }
+ }
+
+ if(((codelist[i]>>>j)&1)==0){ ptr0[ptr]=-i; }
+ else{ ptr1[ptr]=-i; }
+
+ }
+ }
+ //free(codelist);
+
+ t.tabn = ilog(entries)-4;
+
+ if(t.tabn<5)t.tabn=5;
+ int n = 1<<t.tabn;
+ t.tab = new int[n];
+ t.tabl = new int[n];
+ for(int i = 0; i < n; i++){
+ int p = 0;
+ int j=0;
+ for(j = 0; j < t.tabn && (p > 0 || j == 0); j++){
+ if ((i&(1<<j))!=0){
+ p = ptr1[p];
+ }
+ else{
+ p = ptr0[p];
+ }
+ }
+ t.tab[i]=p; // -code
+ t.tabl[i]=j; // length
+ }
+
+ return(t);
+ }
+
+ private static int ilog(int v){
+ int ret=0;
+ while(v!=0){
+ ret++;
+ v>>>=1;
+ }
+ return(ret);
+ }
+
+/*
+ // TEST
+ // Simple enough; pack a few candidate codebooks, unpack them. Code a
+ // number of vectors through (keeping track of the quantized values),
+ // and decode using the unpacked book. quantized version of in should
+ // exactly equal out
+
+ //#include "vorbis/book/lsp20_0.vqh"
+ //#include "vorbis/book/lsp32_0.vqh"
+ //#include "vorbis/book/res0_1a.vqh"
+ static final int TESTSIZE=40;
+
+ static float[] test1={
+ 0.105939,
+ 0.215373,
+ 0.429117,
+ 0.587974,
+
+ 0.181173,
+ 0.296583,
+ 0.515707,
+ 0.715261,
+
+ 0.162327,
+ 0.263834,
+ 0.342876,
+ 0.406025,
+
+ 0.103571,
+ 0.223561,
+ 0.368513,
+ 0.540313,
+
+ 0.136672,
+ 0.395882,
+ 0.587183,
+ 0.652476,
+
+ 0.114338,
+ 0.417300,
+ 0.525486,
+ 0.698679,
+
+ 0.147492,
+ 0.324481,
+ 0.643089,
+ 0.757582,
+
+ 0.139556,
+ 0.215795,
+ 0.324559,
+ 0.399387,
+
+ 0.120236,
+ 0.267420,
+ 0.446940,
+ 0.608760,
+
+ 0.115587,
+ 0.287234,
+ 0.571081,
+ 0.708603,
+ };
+
+ static float[] test2={
+ 0.088654,
+ 0.165742,
+ 0.279013,
+ 0.395894,
+
+ 0.110812,
+ 0.218422,
+ 0.283423,
+ 0.371719,
+
+ 0.136985,
+ 0.186066,
+ 0.309814,
+ 0.381521,
+
+ 0.123925,
+ 0.211707,
+ 0.314771,
+ 0.433026,
+
+ 0.088619,
+ 0.192276,
+ 0.277568,
+ 0.343509,
+
+ 0.068400,
+ 0.132901,
+ 0.223999,
+ 0.302538,
+
+ 0.202159,
+ 0.306131,
+ 0.360362,
+ 0.416066,
+
+ 0.072591,
+ 0.178019,
+ 0.304315,
+ 0.376516,
+
+ 0.094336,
+ 0.188401,
+ 0.325119,
+ 0.390264,
+
+ 0.091636,
+ 0.223099,
+ 0.282899,
+ 0.375124,
+ };
+
+ static float[] test3={
+ 0,1,-2,3,4,-5,6,7,8,9,
+ 8,-2,7,-1,4,6,8,3,1,-9,
+ 10,11,12,13,14,15,26,17,18,19,
+ 30,-25,-30,-1,-5,-32,4,3,-2,0};
+
+// static_codebook *testlist[]={&_vq_book_lsp20_0,
+// &_vq_book_lsp32_0,
+// &_vq_book_res0_1a,NULL};
+ static[][] float testvec={test1,test2,test3};
+
+ static void main(String[] arg){
+ Buffer write=new Buffer();
+ Buffer read=new Buffer();
+ int ptr=0;
+ write.writeinit();
+
+ System.err.println("Testing codebook abstraction...:");
+
+ while(testlist[ptr]!=null){
+ CodeBook c=new CodeBook();
+ StaticCodeBook s=new StaticCodeBook();;
+ float *qv=alloca(sizeof(float)*TESTSIZE);
+ float *iv=alloca(sizeof(float)*TESTSIZE);
+ memcpy(qv,testvec[ptr],sizeof(float)*TESTSIZE);
+ memset(iv,0,sizeof(float)*TESTSIZE);
+
+ System.err.print("\tpacking/coding "+ptr+"... ");
+
+ // pack the codebook, write the testvector
+ write.reset();
+ vorbis_book_init_encode(&c,testlist[ptr]); // get it into memory
+ // we can write
+ vorbis_staticbook_pack(testlist[ptr],&write);
+ System.err.print("Codebook size "+write.bytes()+" bytes... ");
+ for(int i=0;i<TESTSIZE;i+=c.dim){
+ vorbis_book_encodev(&c,qv+i,&write);
+ }
+ c.clear();
+
+ System.err.print("OK.\n");
+ System.err.print("\tunpacking/decoding "+ptr+"... ");
+
+ // transfer the write data to a read buffer and unpack/read
+ _oggpack_readinit(&read,_oggpack_buffer(&write),_oggpack_bytes(&write));
+ if(s.unpack(read)){
+ System.err.print("Error unpacking codebook.\n");
+ System.exit(1);
+ }
+ if(vorbis_book_init_decode(&c,&s)){
+ System.err.print("Error initializing codebook.\n");
+ System.exit(1);
+ }
+ for(int i=0;i<TESTSIZE;i+=c.dim){
+ if(vorbis_book_decodevs(&c,iv+i,&read,1,-1)==-1){
+ System.err.print("Error reading codebook test data (EOP).\n");
+ System.exit(1);
+ }
+ }
+ for(int i=0;i<TESTSIZE;i++){
+ if(fabs(qv[i]-iv[i])>.000001){
+ System.err.print("read ("+iv[i]+") != written ("+qv[i]+") at position ("+i+")\n");
+ System.exit(1);
+ }
+ }
+
+ System.err.print("OK\n");
+ ptr++;
+ }
+ // The above is the trivial stuff;
+ // now try unquantizing a log scale codebook
+ }
+*/
+}
+
+class DecodeAux{
+ int[] tab;
+ int[] tabl;
+ int tabn;
+
+ int[] ptr0;
+ int[] ptr1;
+ int aux; // number of tree entries
+}
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+// the comments are not part of vorbis_info so that vorbis_info can be
+// static storage
+public class Comment{
+ private static byte[] _vorbis="vorbis".getBytes();
+
+ private static final int OV_EFAULT=-129;
+ private static final int OV_EIMPL=-130;
+
+ // unlimited user comment fields. libvorbis writes 'libvorbis'
+ // whatever vendor is set to in encode
+ public byte[][] user_comments;
+ public int[] comment_lengths;
+ public int comments;
+ public byte[] vendor;
+
+ public void init(){
+ user_comments=null;
+ comments=0;
+ vendor=null;
+ }
+
+ public void add(String comment){
+ add(comment.getBytes());
+ }
+
+ private void add(byte[] comment){
+ byte[][] foo=new byte[comments+2][];
+ if(user_comments!=null){
+ System.arraycopy(user_comments, 0, foo, 0, comments);
+ }
+ user_comments=foo;
+
+ int[] goo=new int[comments+2];
+ if(comment_lengths!=null){
+ System.arraycopy(comment_lengths, 0, goo, 0, comments);
+ }
+ comment_lengths=goo;
+
+ byte[] bar=new byte[comment.length+1];
+ System.arraycopy(comment, 0, bar, 0, comment.length);
+ user_comments[comments]=bar;
+ comment_lengths[comments]=comment.length;
+ comments++;
+ user_comments[comments]=null;
+ }
+
+ public void add_tag(String tag, String contents){
+ if(contents==null) contents="";
+ add(tag+"="+contents);
+ }
+
+/*
+ private void add_tag(byte[] tag, byte[] contents){
+ byte[] foo=new byte[tag.length+contents.length+1];
+ int j=0;
+ for(int i=0; i<tag.length; i++){foo[j++]=tag[i];}
+ foo[j++]=(byte)'='; j++;
+ for(int i=0; i<contents.length; i++){foo[j++]=tag[i];}
+ add(foo);
+ }
+*/
+
+ // This is more or less the same as strncasecmp - but that doesn't exist
+ // * everywhere, and this is a fairly trivial function, so we include it
+ static boolean tagcompare(byte[] s1, byte[] s2, int n){
+ int c=0;
+ byte u1, u2;
+ while(c < n){
+ u1=s1[c]; u2=s2[c];
+ if('Z'>=u1 && u1>='A')u1=(byte)(u1-'A'+'a');
+ if('Z'>=u2 && u2>='A')u2=(byte)(u2-'A'+'a');
+ if(u1!=u2){ return false; }
+ c++;
+ }
+ return true;
+ }
+
+ public String query(String tag){
+ return query(tag, 0);
+ }
+
+ public String query(String tag, int count){
+ int foo=query(tag.getBytes(), count);
+ if(foo==-1)return null;
+ byte[] comment=user_comments[foo];
+ for(int i=0; i<comment_lengths[foo]; i++){
+ if(comment[i]=='='){
+ return new String(comment, i+1, comment_lengths[foo]-(i+1));
+ }
+ }
+ return null;
+ }
+
+ private int query(byte[] tag, int count){
+ int i=0;
+ int found = 0;
+ int fulltaglen = tag.length + 1;
+ byte[] fulltag = new byte[fulltaglen];
+ System.arraycopy(tag, 0, fulltag, 0, tag.length);
+ fulltag[tag.length]=(byte)'=';
+
+ for(i=0;i<comments;i++){
+ if(tagcompare(user_comments[i], fulltag, fulltaglen)){
+ if(count==found){
+ // We return a pointer to the data, not a copy
+ //return user_comments[i] + taglen + 1;
+ return i;
+ }
+ else{ found++; }
+ }
+ }
+ return -1;
+ }
+
+ int unpack(Buffer opb){
+ int vendorlen=opb.read(32);
+ if(vendorlen<0){
+ //goto err_out;
+ clear();
+ return(-1);
+ }
+ vendor=new byte[vendorlen+1];
+ opb.read(vendor,vendorlen);
+ comments=opb.read(32);
+ if(comments<0){
+ //goto err_out;
+ clear();
+ return(-1);
+ }
+ user_comments=new byte[comments+1][];
+ comment_lengths=new int[comments+1];
+
+ for(int i=0;i<comments;i++){
+ int len=opb.read(32);
+ if(len<0){
+ //goto err_out;
+ clear();
+ return(-1);
+ }
+ comment_lengths[i]=len;
+ user_comments[i]=new byte[len+1];
+ opb.read(user_comments[i], len);
+ }
+ if(opb.read(1)!=1){
+ //goto err_out; // EOP check
+ clear();
+ return(-1);
+
+ }
+ return(0);
+// err_out:
+// comment_clear(vc);
+// return(-1);
+ }
+
+ int pack(Buffer opb){
+ byte[] temp="Xiphophorus libVorbis I 20000508".getBytes();
+
+ // preamble
+ opb.write(0x03,8);
+ opb.write(_vorbis);
+
+ // vendor
+ opb.write(temp.length,32);
+ opb.write(temp);
+
+ // comments
+
+ opb.write(comments,32);
+ if(comments!=0){
+ for(int i=0;i<comments;i++){
+ if(user_comments[i]!=null){
+ opb.write(comment_lengths[i],32);
+ opb.write(user_comments[i]);
+ }
+ else{
+ opb.write(0,32);
+ }
+ }
+ }
+ opb.write(1,1);
+ return(0);
+ }
+
+ public int header_out(Packet op){
+ Buffer opb=new Buffer();
+ opb.writeinit();
+
+ if(pack(opb)!=0) return OV_EIMPL;
+
+ op.packet_base = new byte[opb.bytes()];
+ op.packet=0;
+ op.bytes=opb.bytes();
+ System.arraycopy(opb.buffer(), 0, op.packet_base, 0, op.bytes);
+ op.b_o_s=0;
+ op.e_o_s=0;
+ op.granulepos=0;
+ return 0;
+ }
+
+ void clear(){
+ for(int i=0;i<comments;i++)
+ user_comments[i]=null;
+ user_comments=null;
+ vendor=null;
+ }
+
+ public String getVendor(){
+ return new String(vendor, 0, vendor.length-1);
+ }
+ public String getComment(int i){
+ if(comments<=i)return null;
+ return new String(user_comments[i], 0, user_comments[i].length-1);
+ }
+ public String toString(){
+ String foo="Vendor: "+new String(vendor, 0, vendor.length-1);
+ for(int i=0; i<comments; i++){
+ foo=foo+"\nComment: "+new String(user_comments[i], 0, user_comments[i].length-1);
+ }
+ foo=foo+"\n";
+ return foo;
+ }
+}
--- /dev/null
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to
+ * Monty <monty@xiph.org> and
+ * The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+// Takes a vorbis bitstream from stdin and writes raw stereo PCM to
+// stdout. Decodes simple and chained OggVorbis files from beginning
+// to end. Vorbisfile.a is somewhat more complex than the code below.
+
+class DecodeExample{
+ static int convsize=4096*2;
+ static byte[] convbuffer=new byte[convsize]; // take 8k out of the data segment, not the stack
+
+ public static void main(String[] arg){
+ java.io.InputStream input=System.in;
+ if(arg.length>0){
+ try{
+ input=new java.io.FileInputStream(arg[0]);
+ }
+ catch(Exception e){
+ System.err.println(e);
+ }
+ }
+
+ SyncState oy=new SyncState(); // sync and verify incoming physical bitstream
+ StreamState os=new StreamState(); // take physical pages, weld into a logical stream of packets
+ Page og=new Page(); // one Ogg bitstream page. Vorbis packets are inside
+ Packet op=new Packet(); // one raw packet of data for decode
+
+ Info vi=new Info(); // struct that stores all the static vorbis bitstream settings
+ Comment vc=new Comment(); // struct that stores all the bitstream user comments
+ DspState vd=new DspState(); // central working state for the packet->PCM decoder
+ Block vb=new Block(vd); // local working space for packet->PCM decode
+
+ byte[] buffer;
+ int bytes=0;
+
+ // Decode setup
+
+ oy.init(); // Now we can read pages
+
+ while(true){ // we repeat if the bitstream is chained
+ int eos=0;
+
+ // grab some data at the head of the stream. We want the first page
+ // (which is guaranteed to be small and only contain the Vorbis
+ // stream initial header) We need the first page to get the stream
+ // serialno.
+
+ // submit a 4k block to libvorbis' Ogg layer
+ int index=oy.buffer(4096);
+ buffer=oy.data;
+ try{
+ bytes=input.read(buffer, index, 4096);
+ }
+ catch(Exception e){
+ System.err.println(e);
+ System.exit(-1);
+ }
+ oy.wrote(bytes);
+
+ // Get the first page.
+ if(oy.pageout(og)!=1){
+ // have we simply run out of data? If so, we're done.
+ if(bytes<4096)break;
+
+ // error case. Must not be Vorbis data
+ System.err.println("Input does not appear to be an Ogg bitstream.");
+ System.exit(1);
+ }
+
+ // Get the serial number and set up the rest of decode.
+ // serialno first; use it to set up a logical stream
+ os.init(og.serialno());
+
+ // extract the initial header from the first page and verify that the
+ // Ogg bitstream is in fact Vorbis data
+
+ // I handle the initial header first instead of just having the code
+ // read all three Vorbis headers at once because reading the initial
+ // header is an easy way to identify a Vorbis bitstream and it's
+ // useful to see that functionality seperated out.
+
+ vi.init();
+ vc.init();
+ if(os.pagein(og)<0){
+ // error; stream version mismatch perhaps
+ System.err.println("Error reading first page of Ogg bitstream data.");
+ System.exit(1);
+ }
+
+ if(os.packetout(op)!=1){
+ // no page? must not be vorbis
+ System.err.println("Error reading initial header packet.");
+ System.exit(1);
+ }
+
+ if(vi.synthesis_headerin(vc,op)<0){
+ // error case; not a vorbis header
+ System.err.println("This Ogg bitstream does not contain Vorbis audio data.");
+ System.exit(1);
+ }
+
+ // At this point, we're sure we're Vorbis. We've set up the logical
+ // (Ogg) bitstream decoder. Get the comment and codebook headers and
+ // set up the Vorbis decoder
+
+ // The next two packets in order are the comment and codebook headers.
+ // They're likely large and may span multiple pages. Thus we reead
+ // and submit data until we get our two pacakets, watching that no
+ // pages are missing. If a page is missing, error out; losing a
+ // header page is the only place where missing data is fatal. */
+
+ int i=0;
+ while(i<2){
+ while(i<2){
+
+ int result=oy.pageout(og);
+ if(result==0) break; // Need more data
+ // Don't complain about missing or corrupt data yet. We'll
+ // catch it at the packet output phase
+
+ if(result==1){
+ os.pagein(og); // we can ignore any errors here
+ // as they'll also become apparent
+ // at packetout
+ while(i<2){
+ result=os.packetout(op);
+ if(result==0)break;
+ if(result==-1){
+ // Uh oh; data at some point was corrupted or missing!
+ // We can't tolerate that in a header. Die.
+ System.err.println("Corrupt secondary header. Exiting.");
+ System.exit(1);
+ }
+ vi.synthesis_headerin(vc,op);
+ i++;
+ }
+ }
+ }
+ // no harm in not checking before adding more
+ index=oy.buffer(4096);
+ buffer=oy.data;
+ try{
+ bytes=input.read(buffer, index, 4096);
+ }
+ catch(Exception e){
+ System.err.println(e);
+ System.exit(1);
+ }
+ if(bytes==0 && i<2){
+ System.err.println("End of file before finding all Vorbis headers!");
+ System.exit(1);
+ }
+ oy.wrote(bytes);
+ }
+
+ // Throw the comments plus a few lines about the bitstream we're
+ // decoding
+ {
+ byte[][] ptr=vc.user_comments;
+ for(int j=0; j<ptr.length;j++){
+ if(ptr[j]==null) break;
+ System.err.println(new String(ptr[j], 0, ptr[j].length-1));
+ }
+ System.err.println("\nBitstream is "+vi.channels+" channel, "+vi.rate+"Hz");
+ System.err.println("Encoded by: "+new String(vc.vendor, 0, vc.vendor.length-1)+"\n");
+ }
+
+ convsize=4096/vi.channels;
+
+ // OK, got and parsed all three headers. Initialize the Vorbis
+ // packet->PCM decoder.
+ vd.synthesis_init(vi); // central decode state
+ vb.init(vd); // local state for most of the decode
+ // so multiple block decodes can
+ // proceed in parallel. We could init
+ // multiple vorbis_block structures
+ // for vd here
+
+ float[][][] _pcm=new float[1][][];
+ int[] _index=new int[vi.channels];
+ // The rest is just a straight decode loop until end of stream
+ while(eos==0){
+ while(eos==0){
+
+ int result=oy.pageout(og);
+ if(result==0)break; // need more data
+ if(result==-1){ // missing or corrupt data at this page position
+ System.err.println("Corrupt or missing data in bitstream; continuing...");
+ }
+ else{
+ os.pagein(og); // can safely ignore errors at
+ // this point
+ while(true){
+ result=os.packetout(op);
+
+ if(result==0)break; // need more data
+ if(result==-1){ // missing or corrupt data at this page position
+ // no reason to complain; already complained above
+ }
+ else{
+ // we have a packet. Decode it
+ int samples;
+ if(vb.synthesis(op)==0){ // test for success!
+ vd.synthesis_blockin(vb);
+ }
+
+ // **pcm is a multichannel float vector. In stereo, for
+ // example, pcm[0] is left, and pcm[1] is right. samples is
+ // the size of each channel. Convert the float values
+ // (-1.<=range<=1.) to whatever PCM format and write it out
+
+ while((samples=vd.synthesis_pcmout(_pcm, _index))>0){
+ &nb