diff options
author | Winfried Ritsch <ritsch@users.sourceforge.net> | 2005-05-03 07:18:08 +0000 |
---|---|---|
committer | Winfried Ritsch <ritsch@users.sourceforge.net> | 2005-05-03 07:18:08 +0000 |
commit | f3f3f15d90ae9c0a8fec183efdfd0dcb8bd054eb (patch) | |
tree | 2e607ed794800edd3da1f95630f44c9f9550d7e2 |
This commit was generated by cvs2svn to compensate for changes in r2884,HEADsvn2git-rootsvn2git-headexternals/iem/iemstream
which included commits to RCS files with non-trunk default branches.
svn path=/trunk/externals/iem/iemstream/; revision=2885
-rw-r--r-- | LICENCE.txt | 360 | ||||
-rw-r--r-- | Makefile | 83 | ||||
-rw-r--r-- | README.txt | 38 | ||||
-rw-r--r-- | fifo.h | 142 | ||||
-rw-r--r-- | help-stream.pd | 96 | ||||
-rw-r--r-- | main.cpp | 241 | ||||
-rw-r--r-- | socket.cpp | 188 | ||||
-rw-r--r-- | socket.h | 21 | ||||
-rw-r--r-- | stream.cpp | 557 | ||||
-rw-r--r-- | stream.dsp | 134 | ||||
-rw-r--r-- | stream.dsw | 29 | ||||
-rw-r--r-- | stream.h | 160 | ||||
-rw-r--r-- | stream.vcproj | 152 | ||||
-rw-r--r-- | streamogg.cpp | 113 | ||||
-rw-r--r-- | streamogg.h | 64 |
15 files changed, 2378 insertions, 0 deletions
diff --git a/LICENCE.txt b/LICENCE.txt new file mode 100644 index 0000000..c626a60 --- /dev/null +++ b/LICENCE.txt @@ -0,0 +1,360 @@ +stream - external library for PD (Puredata) for +streaming multichannel files and others + +Copyright (C) 1998-2005 Institute for Electronic Music and Acoustics, Graz +Author: Thomas Grill + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. (see below) + +--------------------------- GPL.TXT ------------------------- + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..043c987 --- /dev/null +++ b/Makefile @@ -0,0 +1,83 @@ +# stream - stream externals for PD +# +# Makefile for gcc @ linux +# +# usage: +# to build run "make -f Makefile.linux" +# to install (as root), do "make -f Makefile.linux install" +# + +# Configuration: ########################## + + +# where are the PD header files? +# leave it blank if it is a system directory (like /usr/local/include), +# since gcc 3.2 complains about it +PDPATH= + +# where should flext libraries be built? +TARGDIR=./pd-linux + +# where should the external be installed? +# (leave blank to omit installation) +INSTPATH=/usr/local/lib/pd/extra + +# additional compiler flags +# (check if they fit for your system!) +# UFLAGS=-mcpu=pentiumpro # gcc 2.95 +UFLAGS=-mcpu=pentium3 -msse # gcc 3.xx + +########################################### + +# compiler+linker stuff +INCLUDES=$(PDPATH) +LIBPATH= +FLAGS=-DPD ${U_FLAGS} +CFLAGS=-O6 +#CFLAGS=-g +LIBS=m util ogg vorbis vorbisenc vorbisfile samplerate + + +# --------------------------------------------- +# the rest can stay untouched +# ---------------------------------------------- + +NAME=stream + +# all the source files from the package +DIR=. +SRCS=main.cpp socket.cpp stream.cpp streamogg.cpp + +MAKEFILE=Makefile + +TARGET=$(TARGDIR)/$(NAME).pd_linux + +# default target +all: $(TARGDIR) $(TARGET) + +$(patsubst %,$(DIR)/%,$(SRCS)): $(patsubst %,$(DIR)/%,$(HDRS)) $(MAKEFILE) + touch $@ + +$(TARGDIR): + mkdir $(TARGDIR) + +$(TARGDIR)/%.o : $(DIR)/%.cpp + $(CXX) -c $(CFLAGS) $(FLAGS) $(patsubst %,-I%,$(INCLUDES)) $< -o $@ + +$(TARGET) : $(patsubst %.cpp,$(TARGDIR)/%.o,$(SRCS)) + $(CXX) -shared $^ $(patsubst %,-L%,$(LIBPATH)) $(patsubst %,-l%,$(LIBS)) -o $@ + chmod 755 $@ + +$(INSTPATH): + mkdir $(INSTPATH) + +install:: $(INSTPATH) + +install:: $(TARGET) + cp $^ $(INSTPATH) + chown root.root $(patsubst %,$(INSTPATH)/%,$(notdir $^)) + chmod 755 $(patsubst %,$(INSTPATH)/%,$(notdir $^)) + +.PHONY: clean +clean: + rm -f $(TARGDIR)/*.o $(TARGET) diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..feb4643 --- /dev/null +++ b/README.txt @@ -0,0 +1,38 @@ +stream - external library for streaming + +Momentan ein Object amp~ zur Wiedergabe von ogg-Streams. + +Die Idee stammt von readanysf~ (August Black), wurde aber völlig neu implementiert. +Ein paar unwichtige Code-Schnippsel sind noch drin, werden aber auch neu geschrieben. + + +Features (im Vergleich pdogg and readanysf~): +- Mehrkanalfähigkeit +- Genau dosierbare Pufferung +- nur ein thread für Datenempfang und Decodierung +- Stream-Resampling, wenn nötig + + +Abhängigkeiten: +- OGG und Vorbis +- libsamplerate + +Bedienung: +[amp~ n] erzeugt ein Objekt mit n Signal-Outlets. Der Stream kann eine andere Kanalzahl haben. +Überzählige Stream-Kanäle werden ignoriert, überzählige Outlets haben 0~-Signale. + +Messages: +[connect URI( ... Verbinde zu Server:Port/Mountpoint und starte Wiedergabe +[disconnect( .... Stoppe Wiedergabe +[strbuf samples ( ... Stream-Buffer-Größe in Samples (default 10000) +[strchunk samples ( ... Samples die auf einmal vom Socket gelesen werden (default 500) +[strthresh ratio ( ... Threshold 0...1 unter welchem Anteil der Buffer-Füllung nachgeladen wird. (default 0.95) + +[debug 0/1 ( .... Debug-Output zur Konsole + + +Probleme: +resampling funktioniert nicht einwandfrei + +LICENCE: GPL + see LICENCE.txt @@ -0,0 +1,142 @@ +/************************************************************* + * + * streaming external for PD + * + * File: fifo.h + * + * Description: Implementation of a FIFO template class + * + * Author: Thomas Grill (t.grill@gmx.net) + * + *************************************************************/ + +#ifndef _FIFO_H_ +#define _FIFO_H_ + +#include <memory.h> + +#include <assert.h> +#define ASSERT assert + + +/*! FIFO class + \note not thread-safe +*/ +template<class T> +class Fifo +{ +public: + Fifo(int n = 0): size(n), arr(new T[n]),rd(0),wr(0),have(0) {} + ~Fifo() { if(arr) delete[] arr; } + + //! Clear fifo + void Clear() { rd = wr = have = 0; } + + //! Get total fifo size + int Size() const { return size; } + //! Get number of items in fifo + int Have() const { return have; } + //! Get free fifo size + int Free() const { return size-have; } + + //! Get pointer to beginning of contained data + T *ReadPtr() const { return arr+rd; } + //! Get number of items from read pos to end of contiguous space + int ReadSamples() const { return size-rd; } + //! Get pointer to beginning of free space + T *WritePtr() const { return arr+wr; } + //! Get number of items from write pos to end of contiguous space + int WriteSamples() const { return size-wr; } + + /*! Resize FIFO + \param nsz new FIFO size + \param keep keep data? + \return true if all data could be kept + */ + bool Resize(int nsz,bool keep) + { + if(keep) { + bool all = true; + T *narr = new T[nsz]; + // try to keep newest data + int s = Have()-nsz; + if(s > 0) { + // skip items that don't fit into new buffer + all = false; + int r = Read(s,NULL); + ASSERT(r == s); + } + s = Have(); + int r = Read(s,narr); + ASSERT(r == s); + + delete[] arr; + arr = narr; size = nsz; + Clear(); + return all; + } + else { + delete[] arr; + arr = new T[size = nsz]; + Clear(); + return true; + } + } + + /*! Write data to fifo + \param n number of items to write + \param buf memory buffer containing items + \return number of written items + */ + int Write(int n,const T *buf) + { + if(n > Free()) n = Free(); + + if(wr+n >= size) { + // wrap-over + int ch1 = size-wr; + memcpy(arr+wr,buf,ch1*sizeof(T)); + wr = n-ch1; + memcpy(arr,buf+ch1,wr*sizeof(T)); + } + else { + memcpy(arr+wr,buf,n*sizeof(T)); + wr += n; + } + + have += n; + return n; + } + + /*! Read data from fifo + \param n number of items to read + \param buf memory buffer to store items (NULL to simply skip items) + \return number of read items + */ + int Read(int n,T *buf) + { + if(n > Have()) n = Have(); + + if(rd+n >= size) { + // wrap-over + int ch1 = size-rd; + if(buf) memcpy(buf,arr+rd,ch1*sizeof(T)); + rd = n-ch1; + if(buf) memcpy(buf+ch1,arr,rd*sizeof(T)); + } + else { + if(buf) memcpy(buf,arr+rd,n*sizeof(T)); + rd += n; + } + + have -= n; + return n; + } + +protected: + int size; + int rd,wr,have; + mutable T *arr; +}; + +#endif diff --git a/help-stream.pd b/help-stream.pd new file mode 100644 index 0000000..db285c9 --- /dev/null +++ b/help-stream.pd @@ -0,0 +1,96 @@ +#N canvas 431 70 743 474 12; +#X obj 273 303 dac~ 1 2 3 4; +#X obj 227 275 *~ 0; +#X obj 264 275 *~ 0; +#X obj 300 276 *~ 0; +#X obj 337 276 *~ 0; +#X obj 342 244 hsl 128 15 0.001 1 1 1 empty empty empty -2 -6 0 8 -262131 +-1 -1 12000 1; +#X obj 14 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 0 0; +#X obj 74 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 1 0; +#X obj 11 272 env~; +#X obj 118 272 env~; +#X obj 12 298 - 100; +#X obj 119 298 - 100; +#X obj 628 23 loadbang; +#X msg 628 53 \; pd dsp 1; +#X msg 69 84 disconnect; +#X msg 23 11 connect darkstar:8008/ices.ogg; +#X obj 104 147 tgl 15 0 empty empty empty 0 -6 0 8 -260818 -1 -1 0 +1; +#X msg 122 144 debug \$1; +#X msg 43 37 connect dev.iem.at:8002/test.ogg; +#X msg 58 62 connect radio.jcraft.com:8000/test.ogg; +#X obj 215 104 nbx 5 14 0 1e+006 0 1 empty empty empty 0 -6 0 10 -262131 +-1 -1 30000 256; +#X msg 214 123 strbuf \$1; +#X obj 71 200 amp~ 4; +#X obj 34 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 0 0; +#X obj 48 272 env~; +#X obj 49 298 - 100; +#X obj 54 333 vu 15 120 empty empty -1 -8 0 8 -66577 -1 0 0; +#X obj 83 272 env~; +#X obj 84 298 - 100; +#X msg 376 93 getchannels; +#X msg 376 115 getsrate; +#X obj 278 351 route float; +#X obj 376 350 print; +#X obj 213 151 nbx 5 14 0 1e+006 0 1 empty empty empty 0 -6 0 10 -262131 +-1 -1 10 256; +#X msg 212 170 tick \$1; +#X obj 281 388 hsl 128 15 0 1 0 1 empty empty empty -2 -6 0 8 -225271 +-1 -1 0 1; +#X text 276 403 filling ratio; +#X text 340 226 volume; +#X msg 376 138 getbrate; +#X msg 376 160 gettag title; +#X text 472 94 stream channels; +#X text 452 114 stream sample rate; +#X text 452 136 nominal stream bit rate; +#X text 482 161 title tag; +#X msg 376 183 gettag artist; +#X text 487 184 artist tag; +#X text 570 172 etc.; +#X connect 1 0 0 0; +#X connect 2 0 0 1; +#X connect 3 0 0 2; +#X connect 4 0 0 3; +#X connect 5 0 1 1; +#X connect 5 0 2 1; +#X connect 5 0 3 1; +#X connect 5 0 4 1; +#X connect 8 0 10 0; +#X connect 9 0 11 0; +#X connect 10 0 6 0; +#X connect 11 0 7 0; +#X connect 12 0 13 0; +#X connect 14 0 22 0; +#X connect 15 0 22 0; +#X connect 16 0 17 0; +#X connect 17 0 22 0; +#X connect 18 0 22 0; +#X connect 19 0 22 0; +#X connect 20 0 21 0; +#X connect 21 0 22 0; +#X connect 22 0 1 0; +#X connect 22 0 8 0; +#X connect 22 1 2 0; +#X connect 22 1 24 0; +#X connect 22 2 3 0; +#X connect 22 2 27 0; +#X connect 22 3 4 0; +#X connect 22 3 9 0; +#X connect 22 4 31 0; +#X connect 24 0 25 0; +#X connect 25 0 23 0; +#X connect 27 0 28 0; +#X connect 28 0 26 0; +#X connect 29 0 22 0; +#X connect 30 0 22 0; +#X connect 31 0 35 0; +#X connect 31 1 32 0; +#X connect 33 0 34 0; +#X connect 34 0 22 0; +#X connect 38 0 22 0; +#X connect 39 0 22 0; +#X connect 44 0 22 0; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0533361 --- /dev/null +++ b/main.cpp @@ -0,0 +1,241 @@ +/************************************************************* + * + * streaming external for PD + * + * File: main.cpp + * + * Description: PD class definition + * + * Author: Thomas Grill (t.grill@gmx.net) + * + *************************************************************/ + + +//! Version number of this external +#define __STREAM_VERSION "0.1.0" + + +// prevent MSVC "extern" warning +#ifdef _MSC_VER +#pragma warning( disable : 4091 ) +#endif + +// headers contained in the PD distribution +#include <m_pd.h> +#include <pthread.h> + +// include stream class +#include "streamogg.h" + + +//! PD class +static t_class *amp_class = NULL; + +//! PD object structure +struct t_amp +{ + t_object obj; + Stream *stream; + + int channels; + int vecsz; + float srate; + t_sample **outsigs; + t_outlet *dumpout; + t_clock *clk; + int clkrate; +}; + +/*! periodic clock function for output of buffer filling status */ +static void amp_clock(t_amp *x) +{ + outlet_float(x->dumpout,x->stream->getFilling()); + + // retrigger clock + if(x->clkrate) clock_delay(x->clk,x->clkrate); +} + +/*! PD object constructor + \param fchannels number of channels (stream can have less) +*/ +static void *amp_new(t_floatarg fchannels) +{ + int channels = (int)fchannels; + if(channels == 0) channels = 2; + else + if(channels < 1) return NULL; + + // make PD object + t_amp *x = (t_amp *)pd_new(amp_class); + + x->channels = channels; + x->outsigs = new t_sample *[channels]; + x->stream = new StreamOGG; + + // make signal outlets + for(int i = 0; i < channels; i++) + outlet_new(&x->obj, gensym("signal")); + // additional dump outlet (e.g. buffer filling status) + x->dumpout = outlet_new(&x->obj, gensym("anything")); + + // set clock function + x->clk = clock_new(x,(t_method)amp_clock); + x->clkrate = 0; + + return x; + } + +/*! PD object destructor */ +static void amp_free(t_amp *x) +{ + delete[] x->outsigs; + delete x->stream; + clock_free(x->clk); +} + +/*! DSP function -> delegate processing to stream */ +static t_int *amp_perform(t_int *w) +{ + t_amp *x = (t_amp *)w[1]; + + x->stream->doGet(x->channels,x->outsigs,x->vecsz,x->srate); + + return w+2; +} + +/*! set up DSP */ +static void amp_dsp(t_amp *x, t_signal **sp) +{ + x->vecsz = sp[0]->s_n; + x->srate = sp[0]->s_sr; + + // store outlet signal vectors + for(int i = 0; i < x->channels; ++i) + x->outsigs[i] = sp[i]->s_vec; + + dsp_add(amp_perform, 1, x); +} + +/*! Connect to stream + \param s URL of stream +*/ +static void amp_connect(t_amp *x,const t_symbol *s) +{ + x->stream->doInit(s->s_name); +} + +/*! Disconnect from stream */ +static void amp_disconnect(t_amp *x) +{ + x->stream->doExit(); +} + +/*! Set/Clear debug mode + \param f 0/1 ... debug mode off/on +*/ +static void amp_debug(t_amp *x,t_floatarg f) +{ + x->stream->debug = (f != 0); +} + +/*! Set tick rate for buffer filling status + \param f tick rate in ms +*/ +static void amp_tick(t_amp *x,t_floatarg f) +{ + ASSERT(f >= 0); + x->clkrate = (int)f; + if(f) clock_delay(x->clk,f); + else clock_unset(x->clk); +} + +/*! Set decoder FIFO size */ +static void amp_strbuf(t_amp *x,t_floatarg f) +{ + x->stream->setStreamBufSize((int)f); +} + +/*! Set decoder chunk size */ +static void amp_strchunk(t_amp *x,t_floatarg f) +{ + x->stream->setStreamBufChunk((int)f); +} + +/*! Set decoder filling threshold ratio */ +static void amp_strthresh(t_amp *x,t_floatarg f) +{ + x->stream->setStreamBufThresh(f); +} + +/*! Output number of stream channels to dump outlet */ +static void amp_getchannels(t_amp *x) +{ + t_atom a; + SETFLOAT(&a,x->stream->getChannels()); + outlet_anything(x->dumpout,gensym("channels"),1,&a); +} + +/*! Output sample rate to dump outlet */ +static void amp_getsrate(t_amp *x) +{ + t_atom a; + SETFLOAT(&a,x->stream->getSamplerate()); + outlet_anything(x->dumpout,gensym("srate"),1,&a); +} + +/*! Output bitrate to dump outlet */ +static void amp_getbrate(t_amp *x) +{ + t_atom a; + SETFLOAT(&a,x->stream->getBitrate()); + outlet_anything(x->dumpout,gensym("brate"),1,&a); +} + +/*! Output a stream tag to dump outlet + \param sym name of tag to output +*/ +static void amp_gettag(t_amp *x,const t_symbol *sym) +{ + t_atom a[2]; + SETSYMBOL(a+0,const_cast<t_symbol *>(sym)); + SETSYMBOL(a+1,gensym(const_cast<char *>(x->stream->getTag(sym->s_name).c_str()))); + outlet_anything(x->dumpout,gensym("tag"),2,a); +} + + +/*! External setup routine of PD class + \note Must be exported from shared library +*/ +extern "C" +#ifdef _MSC_VER +__declspec(dllexport) +#endif +void stream_setup() +{ + // post some message to the console + post("Stream, version " __STREAM_VERSION ", (C)2003 IEM Graz"); + post("objects: amp~"); + post(""); + + // register the xmlrpc class + amp_class = class_new(gensym("amp~"),(t_newmethod)amp_new,(t_method)amp_free,sizeof(t_amp),0, A_DEFFLOAT,A_NULL); + + // register methods + class_addmethod(amp_class, (t_method)amp_dsp, gensym("dsp"), A_NULL); + + class_addmethod(amp_class, (t_method)amp_connect, gensym("connect"), A_SYMBOL, A_NULL); + class_addmethod(amp_class, (t_method)amp_disconnect, gensym("disconnect"), A_NULL); + + class_addmethod(amp_class, (t_method)amp_debug, gensym("debug"), A_FLOAT, A_NULL); + class_addmethod(amp_class, (t_method)amp_tick, gensym("tick"), A_FLOAT, A_NULL); + + class_addmethod(amp_class, (t_method)amp_strbuf, gensym("strbuf"), A_FLOAT, A_NULL); + class_addmethod(amp_class, (t_method)amp_strchunk, gensym("strchunk"), A_FLOAT, A_NULL); + class_addmethod(amp_class, (t_method)amp_strthresh, gensym("strthresh"), A_FLOAT, A_NULL); + + class_addmethod(amp_class, (t_method)amp_getchannels, gensym("getchannels"), A_NULL); + class_addmethod(amp_class, (t_method)amp_getsrate, gensym("getsrate"), A_NULL); + class_addmethod(amp_class, (t_method)amp_getbrate, gensym("getbrate"), A_NULL); + + class_addmethod(amp_class, (t_method)amp_gettag, gensym("gettag"), A_SYMBOL,A_NULL); +} diff --git a/socket.cpp b/socket.cpp new file mode 100644 index 0000000..75be726 --- /dev/null +++ b/socket.cpp @@ -0,0 +1,188 @@ +/************************************************************* + * + * streaming external for PD + * + * File: socket.cpp + * + * Description: Implementation of socket functions + * + * Author: Thomas Grill (t.grill@gmx.net) + * + *************************************************************/ + +#include "stream.h" + +// explicit definition of PD report functions +extern "C" { + extern void post(char *fmt, ...); + extern void error(char *fmt, ...); +} + +/* Checks if data is waiting on the socket */ +static bool CheckForData(SOCKET sock,int wait = 20) +{ + struct timeval tv; + tv.tv_sec = wait/1000; + tv.tv_usec = (wait%1000)*1000; + + fd_set set; + FD_ZERO(&set); + FD_SET(sock,&set); + + // return true when data is waiting + return ::select((int)sock+1, &set, NULL, NULL, &tv) > 0; +} + +SOCKET Stream::Connect(const char *hostname,const char *mountpoint,int portno) +{ + const int STRBUF_SIZE = 1024; + char request[STRBUF_SIZE]; // string to be sent to server + sockaddr_in server; + + SOCKET sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sockfd == SOCKET_ERROR) { + throw "Error opening socket"; + } + + // get IP address + hostent *hp = gethostbyname(hostname); + if(!hp) { + closesocket(sockfd); + throw "Could not get IP address of hostname"; + } + + server.sin_family = AF_INET; + memcpy(&server.sin_addr,hp->h_addr, hp->h_length); + server.sin_port = htons((unsigned short)portno); + + // try to connect + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) == SOCKET_ERROR) { + closesocket(sockfd); + throw "Connection failed!"; + } + + // check if we can read from the socket + { + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(sockfd,&fdset); + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500; + + int ret = select((int)sockfd + 1, &fdset, NULL, NULL, &tv); + if(ret < 0) { + closesocket(sockfd); + throw "Can not read from socket"; + } + } + + // build up stuff we need to send to server + sprintf(request, "GET /%s HTTP/1.0 \r\nHost: %s\r\nUser-Agent: 0.2\r\nAccept: audio/x-ogg\r\n\r\n", mountpoint, hostname); + + // try to contact server + if(send(sockfd, request, (int)strlen(request), 0) == SOCKET_ERROR) { + closesocket(sockfd); + throw "Could not contact server"; + } + + // read first line of response + int i = 0; + while(i < STRBUF_SIZE-1) { + if( CheckForData(sockfd) ) { + if(recv(sockfd, request+i, 1, 0) <= 0) { + closesocket(sockfd); + throw "Could not read from socket, quitting"; + } + + if(request[i] == '\n') break; + if(request[i] != '\r') i++; + } + } + request[i] = '\0'; + + bool eof = false; + + // parse content of the response... + if(strstr(request, "HTTP/1.0 200 OK")) /* server is ready */ + { + //post(" : IceCast2 server detected"); + + while(!eof) { + i = 0; + while(i < STRBUF_SIZE-1) { + if( CheckForData(sockfd) ) { + if(recv(sockfd, request + i, 1, 0) <= 0) { + closesocket(sockfd); + throw "Could not read from socket, quitting"; + } + + if(request[i] == '\n') /* leave at end of line */ + break; + if(request[i] == 0x0A) /* leave at end of line */ + break; + if(request[i] != '\r') /* go on until 'return' */ + i++; + } + } + + // make it a null terminated string + request[i] = '\0'; + + char *sptr; + if( ( sptr = strstr(request, "application/x-ogg") ) ) { + /* check for content type */ + //post(" : Ogg Vorbis stream found"); + } + + if( ( sptr = strstr(request, "ice-name:") ) ) { + /* display ice-name */ + //post(" : \"%s\"", sptr + 10); + } + + if(i == 0) + // we got last '\r\n' from server + eof = true; + } + } + else if(strstr(request, "HTTP/1.0 404")) { + // file not found + closesocket(sockfd); + throw "File not found"; + } + else { + // wrong server or wrong answer + closesocket(sockfd); + throw request; + } + + //post(" : connected to http://%s:%d/%s", hp->h_name, portno, mountpoint); + + return sockfd; +} + + +void Stream::Disconnect(SOCKET fd) +{ + if(fd != INVALID_SOCKET) { + closesocket(fd); + post("connection closed"); + } +} + +int Stream::Read(SOCKET fd,char *buf,int size,int timeout) +{ + // check for data availability so that recv doesn't block + if(CheckForData(fd,timeout)) { + int ret = recv(fd,buf,size, 0); + if(ret == SOCKET_ERROR) { + post("socket error!"); + return -1; + } + else + return ret; + } + else + return -1; +} diff --git a/socket.h b/socket.h new file mode 100644 index 0000000..ccdb60c --- /dev/null +++ b/socket.h @@ -0,0 +1,21 @@ +#ifndef __SOCKET_H +#define __SOCKET_H + +#ifdef _WIN32 + #include <io.h> + #include <winsock.h> +#else + #include <sys/socket.h> + #include <netinet/in.h> + #include <netinet/tcp.h> + #include <arpa/inet.h> + #include <netdb.h> + #include <unistd.h> + + #define closesocket close + #define SOCKET int + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 +#endif + +#endif diff --git a/stream.cpp b/stream.cpp new file mode 100644 index 0000000..e684443 --- /dev/null +++ b/stream.cpp @@ -0,0 +1,557 @@ +/************************************************************* + * + * streaming external for PD + * + * File: stream.cpp + * + * Description: Implementation of the streamer class + * + * Author: Thomas Grill (t.grill@gmx.net) + * + *************************************************************/ + +#ifdef _WIN32 + #include <windows.h> +#else + #include <unistd.h> + #include <ctype.h> + #define Sleep(ms) usleep((ms)*1000) +#endif + +#include "stream.h" + + +// default size of encoded data fifo +#define ENCSIZE 10000 +// default size for encoding chunk +#define ENCCHUNK 500 +// default ratio for fifo filling +#define ENCTHRESH 0.95f + +// additional buffer frames for resampling algorithm +#define DECMORE 100 + + +// relative thread priority (-2...0) +#define THRPRIOR -1 + +// default time grain to wait on error (ms) +#define WAITGRAIN 100 +// default time until reconnecting (ms) +#define WAITRECONNECT 3000 + + +// explicit definition of report functions +extern "C" { + extern void post(char *fmt, ...); + extern void error(char *fmt, ...); +} + +Stream::Stream(): + encoded(ENCSIZE),encchunk(ENCCHUNK),encthresh(ENCTHRESH), + waitgrain(WAITGRAIN),waitreconnect(WAITRECONNECT), //waitthread(WAITTHREAD), + file(-1), + exit(false),state(ST_IDLE),debug(false), + bufch(0),bufs(NULL),decoded(NULL), + src_channels(0),src_factor(1),src_state(NULL) +{ + pthread_mutex_init(&mutex,NULL); + pthread_cond_init(&cond,NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); + pthread_create(&thrid,&attr,thr_func,this); + pthread_attr_destroy(&attr); +} + +Stream::~Stream() +{ + // cause thread to exit + exit = true; + pthread_cond_signal(&cond); + if(pthread_join(thrid,NULL) != 0) post("join failed"); + + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); + + Reset(); + + if(bufs) delete[] bufs; + if(decoded) delete[] decoded; + if(src_state) { + for(int i = 0; i < bufch; ++i) src_delete(src_state[i]); + delete[] src_state; + } +} + +void Stream::Reset() +{ + if(file >= 0) { Disconnect(file); file = -1; } + encoded.Clear(); + + if(src_state) { + for(int i = 0; i < bufch; ++i) { + src_reset(src_state[i]); + decoded[i].Clear(); + } + } +} + +void Stream::ResetHost() +{ + hostname = mountpt = ""; port = -1; +} + +bool Stream::doInit(const char *url) +{ + if(isInitializing()) { + // this is not completely clean (should be within the lock) but otherwise the + // caller would have to wait for the thread lock + + post("Still initializing %s/%s:%i",hostname.c_str(),mountpt.c_str(),port); + return false; + } + + bool ok = true; + + pthread_mutex_lock(&mutex); + + // close open file + Reset(); + + // try to set host name, mount point, port number + if(ok) { + char *err = "Invalid URL"; + try { ok = SetURL(url); } + catch(char *tx) { err = tx; ok = false; } + catch(...) { ok = false; } + + if(!ok) { post(err); ResetHost(); } + } + + if(ok) { + state = ST_INIT; + pthread_cond_signal(&cond); + } + + pthread_mutex_unlock(&mutex); + + // let the thread worker do the rest + + return ok; +} + +bool Stream::doExit() +{ + pthread_mutex_lock(&mutex); + + Reset(); + ResetHost(); + state = ST_IDLE; + + pthread_mutex_unlock(&mutex); + + return true; +} + +/*! Get sample frames + \param buf pointer array to channel buffers + \param frames number of maximum frames to get + \return frames put into buffers +*/ +int Stream::doGet(int ch,float *const *buf,int frames,float sr) +{ + ASSERT(ch > 0 && frames >= 0 && sr > 0); + + if(isOk() && !isInitializing()) { + // signal thread worker + pthread_cond_signal(&cond); + + // check/(re)allocate buffers + + int strch = getChannels(); + if(bufs && bufch < strch) { + delete[] decoded; + for(int i = 0; i < bufch; ++i) src_delete(src_state[i]); + delete[] src_state; + delete[] bufs; bufs = NULL; + } + + if(!bufs) { + if(bufch < strch) bufch = strch; + bufs = new float *[bufch]; + decoded = new Fifo<float>[bufch]; + + src_state = new SRC_STATE *[bufch]; + for(int i = 0; i < bufch; ++i) { + int error; + src_state[i] = src_new(SRC_ZERO_ORDER_HOLD,1,&error); + if(!src_state[i]) post("src init error %i",error); + } + } + + // get frames + + float ratio = sr/getSamplerate(); + int frneed = (int)(frames/ratio)+DECMORE; // number of frames to read from decoder fifo + + if(decoded[0].Size() < frneed) { + // fifos are too small -> resize them (while keeping their contents) + for(int i = 0; i < bufch; ++i) decoded[i].Resize(frneed,true); + } + + // how many frames do we need to get from decoder? + int frread = frneed-decoded[0].Have(); + + int ret = state == ST_WAIT?0:DataRead(frread); + + if(ret < 0) { + if(debug) post("read error"); + // clear output + for(int c = 0; c < ch; ++c) + memset(buf[c],0,frames*sizeof *buf[c]); + return 0; + } + else { + // how many channels do we really need for output? + // this should be set elsewhere, because we can't change anyway!!! + // (SRC_STATE for dangling channels would be incorrect) + int cmin = strch; + if(ch < cmin) cmin = ch; + + // write data to fifo + for(int i = 0; i < cmin; ++i) { + int wr = decoded[i].Write(ret,bufs[i]); + if(wr < ret) post("fifo overflow"); + } + +// state = ST_PROCESS; + + if(ratio == 1) { + // no resampling necessary + + // hopefully all channel fifos advance uniformly..... + for(int i = 0; i < cmin; ++i) { + + for(int got = 0; got < frames; ) { + int cnt = frames-got; + + if(decoded[i].Have()) { + got += decoded[i].Read(cnt,buf[i]+got); + } + else { + state = ST_WAIT; + if(debug) post("fifo underrun"); + + // Buffer underrun!! -> zero output buffer + memset(buf[i]+got,0,cnt*sizeof(*buf[i])); + got += cnt; + } + } + } + } + else + { + SRC_DATA src_data; + src_data.src_ratio = ratio; + src_data.end_of_input = 0; + + // hopefully all channel fifos advance uniformly..... + for(int i = 0; i < cmin; ++i) { + src_set_ratio(src_state[i],ratio); + + for(int got = 0; got < frames; ) { + src_data.data_out = buf[i]+got; + src_data.output_frames = frames-got; + + if(decoded[i].Have()) { + src_data.data_in = decoded[i].ReadPtr(); + src_data.input_frames = decoded[i].ReadSamples(); + + int err = src_process(src_state[i],&src_data); + if(err) post("src_process error %i",err); + + // advance buffer + decoded[i].Read(src_data.input_frames_used,NULL); + } + else { + state = ST_WAIT; + if(debug) post("fifo underrun"); + + // Buffer underrun!! -> zero output buffer + memset(src_data.data_out,0,src_data.output_frames*sizeof(*src_data.data_out)); + src_data.output_frames_gen = src_data.output_frames; + } + got += src_data.output_frames_gen; + } + } + } + + // zero remaining channels + for(int c = cmin; c < ch; ++c) + memset(buf[c],0,frames*sizeof *buf[c]); + + return ret; + } + } + else { + for(int c = 0; c < ch; ++c) + memset(buf[c],0,frames*sizeof *buf[c]); + return 0; + } +} + +#define MAXZEROES 5 + +/*! + \param chunk amount of data to read + \param unlock unlock mutex +*/ +int Stream::ReadChunk(int chunk,bool unlock) +{ + if(chunk <= 0) return 0; + + bool ok = true; + char tmp[1024]; + int n = 0,errcnt = 0; + while(ok) { + int c = chunk-n; + if(c <= 0) break; // read enough data + if(c > sizeof tmp) c = sizeof tmp; + SOCKET fd = file; + + if(unlock) pthread_mutex_unlock(&mutex); + + int ret = Read(fd, tmp, c); + + if(unlock) pthread_mutex_lock(&mutex); + + if(ret < 0 || (!ret && ++errcnt == MAXZEROES)) { + if(debug) post("Receive error"); + ok = false; + } + else if(ret > 0) { + if(debug) post("read %i bytes",ret); + errcnt = 0; + encoded.Write(ret,tmp); + n += ret; + } + } + return n; +} + +/*! + \param buf data buffer + \param chunk amount of data to read + \param unlock unlock mutex +*/ +int Stream::ReadChunk(char *buf,int chunk,bool unlock) +{ + if(chunk <= 0) return 0; + + bool ok = true; + int n = 0,errcnt = 0; + while(ok) { + int c = chunk-n; + if(c <= 0) break; // read enough data + SOCKET fd = file; + + if(unlock) pthread_mutex_unlock(&mutex); + + int ret = Read(fd, buf+n, c); + + if(unlock) pthread_mutex_lock(&mutex); + + if(ret < 0 || (!ret && ++errcnt == MAXZEROES)) { + if(debug) post("Receive error"); + ok = false; + } + else if(ret > 0) { + if(debug) post("read %i bytes",ret); + errcnt = 0; + n += ret; + } + } + return n; +} + +#define MAXINITTRIES 5 + +/*! static pthreads thread function */ +void *Stream::thr_func(void *th) +{ + ((Stream *)th)->Work(); + return NULL; +} + +/*! Thread worker - fill the fifo with socket data */ +void Stream::Work() +{ + int waittime = 0; + + // lower thread priority + { + struct sched_param parm; + int policy; + if(pthread_getschedparam(pthread_self(),&policy,&parm) >= 0) { + int minprio = sched_get_priority_min(policy); + + if(debug) post("priority was %i (min = %i)",parm.sched_priority,minprio); + + parm.sched_priority += THRPRIOR; + + if(parm.sched_priority < minprio) parm.sched_priority = minprio; + pthread_setschedparam(pthread_self(),policy,&parm); + } + + if(pthread_getschedparam(pthread_self(),&policy,&parm) >= 0) { + if(debug) post("priority set to %i",parm.sched_priority); + } + } + + while(!exit) { + pthread_mutex_lock(&mutex); + + bool wait = true; + + if(!hostname.length() || !mountpt.length() || port < 0) {} + else + if(state == ST_INIT || state == ST_RECONNECT) { + // initialize! + + bool ok = true; + + try { + file = Connect( hostname.c_str(),mountpt.c_str(),port); + } + catch(char *str) { + if(state != ST_RECONNECT) post(str); + ok = false; + } + catch(...) { + post("Unknown error while connecting"); + ok = false; + } + + // initialize decoder + if(ok) ok = WorkInit(); + + // try to fill buffer + if(ok) { + int i,lim = (int)(encoded.Size()*encthresh); + for(i = MAXINITTRIES; i > 0 && encoded.Have() < lim; ) { + int n = ReadChunk(encoded.Free(),true); + if(!n) --i; + } + if(!i) ok = false; + } + + if(!ok) { + Reset(); + + if(state == ST_INIT) state = ST_IDLE; + // if reconnecting keep on doing that... + } + else { + state = ST_PROCESS; + waittime = 0; + } + } + else if(isOk()) { + SOCKET fd = file; + int chunk = encoded.Free(); + if(chunk > encchunk) chunk = encchunk; + + if(chunk) { + int n = ReadChunk(chunk,true); + + if(n == 0) { + if(debug) post("error receiving data"); + state = ST_WAIT; + } + else + // reset error state + state = ST_PROCESS; + } + + if(encoded.Have() < encoded.Size()*encthresh) + // immediately get the next chunk + wait = false; + } + + if(debug && encoded.Free()) { + post("fifo: sz/fill = %5i/%3.0f%%",encoded.Size(),(float)encoded.Have()/encoded.Size()*100); + } + + if(state == ST_WAIT) { + if(debug) post("Wait for data"); + Sleep(waitgrain); + waittime += waitgrain; + if(waittime > waitreconnect) { + if(debug) post("do reconnect"); + state = ST_RECONNECT; + } + wait = false; + } + else if(state == ST_RECONNECT) { + if(debug) post("Reconnecting again"); + Sleep(waitgrain); + wait = false; + } + + + if(wait) pthread_cond_wait(&cond,&mutex); + + pthread_mutex_unlock(&mutex); + } + + state = ST_FINISHED; +} + +bool Stream::SetURL(const char *url) +{ + char *p = (char *)url; + + // strip prefixes + if(!strncmp(p, "http://", 7)) p += 7; + if(!strncmp(p, "ftp://", 6)) p += 6; + + char *hostptr = p; // points to host name + + char *pathptr = strchr(hostptr,'/'); + if(pathptr) + // / found -> skip / + ++pathptr; + else + // no / found!! ILLEGAL + throw "URL path not found"; + + // get port number + int portno; + char *portptr = strchr(hostptr,':'); + if(portptr && portptr < pathptr) { + portptr++; + int sl = (int)(pathptr-portptr-1); + char *p0 = new char[sl+1]; + ASSERT(p0); + strncpy(p0,portptr,sl); + p0[sl] = 0; + + for(p = p0; *p && isdigit(*p); p++) ; + *p = 0; + + // convert port from string to int + portno = (int)strtol(p0, NULL, 10); + delete[] p0; + } + else + portno = 8000; + + // assign found things to function parameters + hostname = std::string(hostptr,(portptr?portptr:pathptr)-1-hostptr); + mountpt = pathptr; + port = portno; + + return true; +} diff --git a/stream.dsp b/stream.dsp new file mode 100644 index 0000000..4f4e4f6 --- /dev/null +++ b/stream.dsp @@ -0,0 +1,134 @@ +# Microsoft Developer Studio Project File - Name="stream" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** NICHT BEARBEITEN ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=stream - Win32 Debug +!MESSAGE Dies ist kein gültiges Makefile. Zum Erstellen dieses Projekts mit NMAKE +!MESSAGE verwenden Sie den Befehl "Makefile exportieren" und führen Sie den Befehl +!MESSAGE +!MESSAGE NMAKE /f "stream.mak". +!MESSAGE +!MESSAGE Sie können beim Ausführen von NMAKE eine Konfiguration angeben +!MESSAGE durch Definieren des Makros CFG in der Befehlszeile. Zum Beispiel: +!MESSAGE +!MESSAGE NMAKE /f "stream.mak" CFG="stream - Win32 Debug" +!MESSAGE +!MESSAGE Für die Konfiguration stehen zur Auswahl: +!MESSAGE +!MESSAGE "stream - Win32 Release" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE "stream - Win32 Debug" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "stream - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0xc07 /d "NDEBUG" +# ADD RSC /l 0xc07 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 + +!ELSEIF "$(CFG)" == "stream - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "d:\pd\src" /I "D:\IAEM\oggvorbis-win32sdk-1.0.1\include" /I "D:\IAEM\libsamplerate-0.1.2\src" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "STREAM_EXPORTS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0xc07 /d "_DEBUG" +# ADD RSC /l 0xc07 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 pd.lib pthreadVC.lib Ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib libsamplerate.lib ogg.lib vorbis.lib vorbisfile.lib /nologo /dll /debug /machine:I386 /pdbtype:sept /libpath:"d:\pd\bin" /libpath:"D:\IAEM\oggvorbis-win32sdk-1.0.1\lib" /libpath:"D:\IAEM\libsamplerate-0.1.2" + +!ENDIF + +# Begin Target + +# Name "stream - Win32 Release" +# Name "stream - Win32 Debug" +# Begin Group "Quellcodedateien" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\main.cpp +# End Source File +# Begin Source File + +SOURCE=.\socket.cpp +# End Source File +# Begin Source File + +SOURCE=.\stream.cpp +# End Source File +# Begin Source File + +SOURCE=.\streamogg.cpp +# End Source File +# End Group +# Begin Group "Header-Dateien" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\fifo.h +# End Source File +# Begin Source File + +SOURCE=.\socket.h +# End Source File +# Begin Source File + +SOURCE=.\stream.h +# End Source File +# Begin Source File + +SOURCE=.\streamogg.h +# End Source File +# End Group +# Begin Group "Ressourcendateien" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/stream.dsw b/stream.dsw new file mode 100644 index 0000000..78fd028 --- /dev/null +++ b/stream.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNUNG: DIESE ARBEITSBEREICHSDATEI DARF NICHT BEARBEITET ODER GELÖSCHT WERDEN! + +############################################################################### + +Project: "stream"=.\stream.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/stream.h b/stream.h new file mode 100644 index 0000000..b55c8ed --- /dev/null +++ b/stream.h @@ -0,0 +1,160 @@ +/************************************************************* + * + * streaming external for PD + * + * File: stream.h + * + * Description: Declaration of the streamer class + * + * Author: Thomas Grill (t.grill@gmx.net) + * + *************************************************************/ + +#ifndef __STREAM_H +#define __STREAM_H + +#include <pthread.h> + +#include <assert.h> +#define ASSERT assert + +#include <string> + +#include <samplerate.h> + +#include "fifo.h" +#include "socket.h" + + +/*! Class representing an abstract stream. + \note Must be inherited for a special stream format +*/ +class Stream +{ +public: + Stream(); + virtual ~Stream(); + + //! Return true if in initializing state + bool isInitializing() const { return state == ST_INIT || state == ST_RECONNECT; } + //! Return true if socket is valid + bool isOk() const { return file >= 0; } + + // Get/set size of encoder FIFO + int getStreamBufSize() const { return encoded.Size(); } + bool setStreamBufSize(int sz) { return encoded.Resize(sz,true); } + + // Get/set size of encoder chunk (amount of data written from the stream at once) + int getStreamBufChunk() const { return encchunk; } + void setStreamBufChunk(int sz) { if(encchunk > 0) encchunk = sz; } + + // Get/set buffer size ratio under which refilling is triggered (0...1) + float getStreamBufThresh() const { return encthresh; } + void setStreamBufThresh(float r) { if(encthresh > 0 && encthresh <= 1) encthresh = r; } + + //! Get ratio of filling (0...1) + float getFilling() const { return (float)encoded.Have()/encoded.Size(); } + + //! name of stream + virtual std::string getTag(const char *tag) const { return ""; } + //! number of stream channels + virtual int getChannels() const = 0; + //! stream sample rate + virtual float getSamplerate() const = 0; + //! nominal stream bit rate + virtual float getBitrate() const = 0; + + const std::string &getHostname() const { return hostname; } + const std::string &getMountpoint() const { return mountpt; } + int getPort() const { return port; } + + /*! Initialize stream */ + bool doInit(const char *url); + + /*! Disconnect from stream */ + bool doExit(); + + //! Get a number of sample frames + int doGet(int ch,float *const *buf,int frames,float sr); + + // Debug flag + volatile bool debug; + +protected: + + //! Reset encoder state (disconnect and clear FIFOs) + virtual void Reset(); + //! Reset URL + void ResetHost(); + + //! Init decoder + virtual bool WorkInit() = 0; + //! Decode data to channel buffers + virtual int DataRead(int frames) = 0; + + //! Read stream data to encoder FIFO + int ReadChunk(int chunk,bool unlock); + //! Read stream data to buffer + int ReadChunk(char *buf,int chunk,bool unlock); + + //! Set hostname, mountpt, port + bool SetURL(const char *url); + + std::string hostname,mountpt; + int port; + + // --- FIFO for encoded stream data ----------------------- + + int encchunk; //! Size of data chunk to get from socket at once + float encthresh; //! Ratio of fifo filling to keep up to + Fifo<char> encoded; //! Fifo for encoded stream data + + // --- low-level socket stuff ------------------------------ + + //! stream socket + volatile SOCKET file; + + //! Connect to stream + static SOCKET Connect(const char *hostname,const char *mountpoint,int portno); + //! Disonnect from stream + static void Disconnect(SOCKET fd); + //! Read data from stream + static int Read(SOCKET fd,char *buf,int size,int timeout = 1000); + + // --- threading stuff ------------------------------------- + + //! status type + enum state_t { + ST_IDLE, // nothing to do + ST_INIT, // shall connect + ST_PROCESS, // do decoding + ST_WAIT, // wait a bit + ST_RECONNECT, // try to reconnect + ST_FINISHED // waiting for shutdown + }; + + volatile bool exit; //! exit flag + volatile state_t state; //! decoder state + pthread_mutex_t mutex; //! thread mutex + pthread_cond_t cond; //! thread conditional + pthread_t thrid; //! worker thread ID + + int waitgrain,waitreconnect; + + static void *thr_func(void *th); + void Work(); + + // --- channel buffers -------------------------------------- + + int bufch; + float **bufs; + Fifo<float> *decoded; + + // --- SRC stuff -------------------------------------------- + + int src_channels; + double src_factor; + SRC_STATE **src_state; +}; + +#endif // __STREAM_H diff --git a/stream.vcproj b/stream.vcproj new file mode 100644 index 0000000..cc5fd30 --- /dev/null +++ b/stream.vcproj @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="stream" + ProjectGUID="{993DDC78-8370-4E3C-9AE6-EFCF8BA92FD7}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="Debug" + IntermediateDirectory="Debug" + ConfigurationType="2" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""f:\prog\pd\pd-cvs\src";f:\prog\packs\pthreads;F:\prog\audio\libsamplerate\src;F:\prog\audio\xiph\ogg\include;F:\prog\audio\xiph\vorbis\include" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;READ_VORBIS_URL" + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="pthreadVC.lib wsock32.lib" + OutputFile="$(OutDir)/stream.dll" + LinkIncremental="2" + AdditionalLibraryDirectories="f:\prog\packs\pthreads" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/stream.pdb" + SubSystem="2" + ImportLibrary="$(OutDir)/stream.lib" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="Release" + IntermediateDirectory="Release" + ConfigurationType="2" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="3" + GlobalOptimizations="TRUE" + AdditionalIncludeDirectories=""f:\prog\pd\pd-cvs\src";f:\prog\packs\pthreads;F:\prog\audio\libsamplerate\src;F:\prog\audio\xiph\ogg\include;F:\prog\audio\xiph\vorbis\include" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;undefined_EXPORTS" + RuntimeLibrary="0" + EnableEnhancedInstructionSet="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="pthreadVC.lib wsock32.lib" + OutputFile="$(OutDir)/stream.dll" + LinkIncremental="1" + AdditionalLibraryDirectories="f:\prog\packs\pthreads" + GenerateDebugInformation="FALSE" + SubSystem="2" + OptimizeReferences="2" + EnableCOMDATFolding="2" + ImportLibrary="$(OutDir)/stream.lib" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\fifo.h"> + </File> + <File + RelativePath=".\main.cpp"> + </File> + <File + RelativePath=".\readme.txt"> + </File> + <File + RelativePath=".\socket.cpp"> + </File> + <File + RelativePath=".\socket.h"> + </File> + <File + RelativePath=".\stream.cpp"> + </File> + <File + RelativePath=".\stream.h"> + </File> + <File + RelativePath=".\streamogg.cpp"> + </File> + <File + RelativePath=".\streamogg.h"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/streamogg.cpp b/streamogg.cpp new file mode 100644 index 0000000..947753b --- /dev/null +++ b/streamogg.cpp @@ -0,0 +1,113 @@ +/************************************************************* + * + * streaming external for PD + * + * File: streamogg.cpp + * + * Description: Implementation of the streamer class for OGG/vorbis + * + * Author: Thomas Grill (t.grill@gmx.net) + * + *************************************************************/ + +#include "streamogg.h" + + +// explicit definition of report functions +extern "C" { + extern void post(char *fmt, ...); + extern void error(char *fmt, ...); +} + + +StreamOGG::StreamOGG(): + ov_inf(NULL),ov_comm(NULL) +{ + // set OGG callbacks + callbacks.read_func = read_func; + callbacks.seek_func = seek_func; + callbacks.close_func = close_func; + callbacks.tell_func = tell_func; +} + +void StreamOGG::Reset() +{ + Stream::Reset(); + ov_inf = NULL; + ov_comm = NULL; +} + +std::string StreamOGG::getTag(const char *tag) const +{ + const char *c = NULL; + if(ov_comm) c = vorbis_comment_query(ov_comm,const_cast<char *>(tag),0); + return c?c:""; +} + + +#define MAXINITTRIES 5 + +bool StreamOGG::WorkInit() +{ + bool ok = true; + + // read in enough data to represent the OGG header + char hdrbuf[8500]; + int i,need = sizeof hdrbuf; + for(i = MAXINITTRIES; i > 0 && need > 0; ) { + int n = ReadChunk(hdrbuf,need,true); + if(n) + need -= n; + else { + --i; + if(debug) post("Try to init again (%i)....",i); + } + } + if(!i) ok = false; + + // got n bytes into fifo + + if(ok && ov_open_callbacks(this,&ov_file,hdrbuf,sizeof hdrbuf,callbacks) < 0 ) { + post("Header format error"); + ok = false; + } + else { + ov_inf = ov_info(&ov_file,-1); + ov_comm = ov_comment(&ov_file,-1); + } + + return ok; +} + +int StreamOGG::DataRead(int frames) +{ + int current_section; + return ov_read_float(&ov_file,&bufs,frames,¤t_section); +} + +size_t StreamOGG::read_func(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + StreamOGG *str = (StreamOGG *)datasource; + + if(!str->isInitializing()) pthread_mutex_lock(&str->mutex); + + int ret = str->encoded.Read(size*nmemb,(char *)ptr); + + if(str->debug) post("read %i of %i bytes from fifo",ret,size*nmemb); + + if(!str->isInitializing()) pthread_mutex_unlock(&str->mutex); + + return ret; +} + +int StreamOGG::close_func(void *datasource) +{ + post("Closing stream"); + + StreamOGG *str = (StreamOGG *)datasource; + pthread_mutex_lock(&str->mutex); + str->Reset(); + pthread_mutex_unlock(&str->mutex); + return 0; +} + diff --git a/streamogg.h b/streamogg.h new file mode 100644 index 0000000..568e9c2 --- /dev/null +++ b/streamogg.h @@ -0,0 +1,64 @@ +/************************************************************* + * + * streaming external for PD + * + * File: streamogg.h + * + * Description: Declaration of the streamer class for OGG/vorbis + * + * Author: Thomas Grill (t.grill@gmx.net) + * + *************************************************************/ + +#ifndef __STREAMOGG_H +#define __STREAMOGG_H + +#include "stream.h" + +extern "C" { +#include <vorbis/codec.h> +#include <vorbis/vorbisfile.h> +} + + +//! Class representing an OGG stream. +class StreamOGG: + public Stream +{ +public: + StreamOGG(); + + //! Get named tag of stream + virtual std::string getTag(const char *tag) const; + + //! Get number of stream channels + virtual int getChannels() const { return ov_inf?ov_inf->channels:0; } + //! Get stream sample rate + virtual float getSamplerate() const { return ov_inf?(float)ov_inf->rate:0; } + //! Get nominal stream bit rate + virtual float getBitrate() const { return ov_inf?(float)ov_inf->bitrate_nominal:0; } + +protected: + + //! Reset encoder state (disconnect and clear FIFOs) + virtual void Reset(); + + //! Init decoder + virtual bool WorkInit(); + //! Decode data to channel buffers + virtual int DataRead(int frames); + + // OGG/Vorbis data + OggVorbis_File ov_file; + ov_callbacks callbacks; + vorbis_info *ov_inf; + vorbis_comment *ov_comm; + + // OGG callbacks + static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource); + static int close_func(void *datasource); + static int seek_func(void *datasource, ogg_int64_t offset, int whence) { return -1; } + static long tell_func(void *datasource) { return -1; } +}; + +#endif // __STREAMOGG_H |