From f3f3f15d90ae9c0a8fec183efdfd0dcb8bd054eb Mon Sep 17 00:00:00 2001 From: Winfried Ritsch Date: Tue, 3 May 2005 07:18:08 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r2884, which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/iem/iemstream/; revision=2885 --- LICENCE.txt | 360 +++++++++++++++++++++++++++++++++++++ Makefile | 83 +++++++++ README.txt | 38 ++++ fifo.h | 142 +++++++++++++++ help-stream.pd | 96 ++++++++++ main.cpp | 241 +++++++++++++++++++++++++ socket.cpp | 188 +++++++++++++++++++ socket.h | 21 +++ stream.cpp | 557 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ stream.dsp | 134 ++++++++++++++ stream.dsw | 29 +++ stream.h | 160 +++++++++++++++++ stream.vcproj | 152 ++++++++++++++++ streamogg.cpp | 113 ++++++++++++ streamogg.h | 64 +++++++ 15 files changed, 2378 insertions(+) create mode 100644 LICENCE.txt create mode 100644 Makefile create mode 100644 README.txt create mode 100644 fifo.h create mode 100644 help-stream.pd create mode 100644 main.cpp create mode 100644 socket.cpp create mode 100644 socket.h create mode 100644 stream.cpp create mode 100644 stream.dsp create mode 100644 stream.dsw create mode 100644 stream.h create mode 100644 stream.vcproj create mode 100644 streamogg.cpp create mode 100644 streamogg.h 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. + + + Copyright (C) 19yy + + 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. + + , 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 diff --git a/fifo.h b/fifo.h new file mode 100644 index 0000000..0ed7843 --- /dev/null +++ b/fifo.h @@ -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 + +#include +#define ASSERT assert + + +/*! FIFO class + \note not thread-safe +*/ +template +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 +#include + +// 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(sym)); + SETSYMBOL(a+1,gensym(const_cast(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 + #include +#else + #include + #include + #include + #include + #include + #include + + #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 +#else + #include + #include + #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[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 + +#include +#define ASSERT assert + +#include + +#include + +#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 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 *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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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(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 +#include +} + + +//! 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 -- cgit v1.2.1