aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans-Christoph Steiner <eighthave@users.sourceforge.net>2003-04-02 15:46:30 +0000
committerHans-Christoph Steiner <eighthave@users.sourceforge.net>2003-04-02 15:46:30 +0000
commitbadcb8e53fece5b0d573c6118c4ffdb8289e9e16 (patch)
treeee96e6e5a244aec71120c22c770a59a767aca89c
parentc46992b703c81de0b69e9ffc5fd4d385dd894dac (diff)
This commit was generated by cvs2svn to compensate for changes in r526,
which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/olafmatt/; revision=527
-rw-r--r--shoutamp~/shoutamp~.c1780
1 files changed, 1780 insertions, 0 deletions
diff --git a/shoutamp~/shoutamp~.c b/shoutamp~/shoutamp~.c
new file mode 100644
index 0000000..cb70266
--- /dev/null
+++ b/shoutamp~/shoutamp~.c
@@ -0,0 +1,1780 @@
+/* ------------------------- shoutamp~ ---------------------------------------- */
+/* */
+/* Tilde object to receive an mp3-stream from a shoutcast/icecast server. */
+/* Written by Olaf Matthes <olaf.matthes@gmx.de> */
+/* Graphical buffer status display by Yves Degoyon <ydegoyon@free.fr>. */
+/* Get source at http://www.akustische-kunst.org/puredata/shout/ */
+/* */
+/* This library is free software; you can redistribute it and/or */
+/* modify it under the terms of the GNU Lesser General Public */
+/* License as published by the Free Software Foundation; either */
+/* version 2 of the License, or (at your option) any later version. */
+/* */
+/* This library is distributed in the hope that it will be useful, */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */
+/* Lesser General Public License for more details. */
+/* */
+/* You should have received a copy of the GNU Lesser General Public */
+/* License along with this library; if not, write to the */
+/* Free Software Foundation, Inc., 59 Temple Place - Suite 330, */
+/* Boston, MA 02111-1307, USA. */
+/* */
+/* Based on PureData by Miller Puckette and others. */
+/* Uses the LAME MPEG 1 Layer 3 encoding library which can be found at */
+/* http://www.mp3dev.org */
+/* */
+/* ---------------------------------------------------------------------------- */
+
+ /* Pd includes */
+#include "m_pd.h"
+#include "g_canvas.h"
+ /* mpg123 includes (from Lame 3.92) */
+#include "mpg123.h"
+#include "mpglib.h"
+#include "interface.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#ifdef UNIX
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#define SOCKET_ERROR -1
+#else
+#include <io.h> /* for 'write' in pute-function only */
+#include <winsock.h>
+#include <winbase.h>
+#endif
+
+#ifdef NT
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4305 )
+#endif
+
+#ifndef NT
+#define sys_closesocket close
+#define STRDUP strdup
+#else /* if NT */
+#define sys_closesocket closesocket
+#define STRDUP _strdup
+#endif
+
+
+/************************* shoutamp~ object ******************************/
+
+/*
+ Each instance of shoutamp~ owns a "child" thread for doing the data
+ transfer. The parent thread signals the child each time:
+ (1) a connection wants opening or closing;
+ (2) we've eaten another 1/16 of the shared buffer (so that the
+ child thread should check if it's time to receive some more.)
+ The child signals the parent whenever a receive has completed. Signalling
+ is done by setting "conditions" and putting data in mutex-controlled common
+ areas.
+*/
+
+
+#define REQUEST_NOTHING 0
+#define REQUEST_CONNECT 1
+#define REQUEST_CLOSE 2
+#define REQUEST_QUIT 3
+#define REQUEST_BUSY 4
+#define REQUEST_DATA 5
+#define REQUEST_RECONNECT 6
+
+#define STATE_IDLE 0
+#define STATE_STARTUP 1 /* connecting and filling the buffer */
+#define STATE_STREAM 2 /* streaming aund audio output */
+
+#define READSIZE 131072 /* size of mp3 stream receive buffer */
+#define READ 4096 /* amount of mp3 data we pass on to decoder */
+#define DECODE_PACKET_SIZE 32768 /* max size of the data returned by mpglib : 32k */
+#define DEFBUFPERCHAN 262144 /* audio output buffer default: 256k */
+#define MINBUFSIZE (2 * DECODE_PACKET_SIZE) /* minimum buffer size (64k) */
+#define MAXBUFSIZE 16777216 /* arbitrary; just don't want to hang malloc */
+#define STRBUF_SIZE 1024 /* char received from server on startup */
+#define OBJWIDTH 68 /* width of buffer status display */
+#define OBJHEIGHT 10 /* height of buffer status display */
+#define MAXSTREAMCHANS 2 /* maximum number of channels: restricted to 2 by now */
+#define MAX_DECODERS 10 /* maximum number of decoders / object instances */
+
+ /* useful debugging functions from mpglib */
+extern int decode_header( struct frame* fr, unsigned long newhead );
+extern void print_header_compact( struct frame* fr );
+extern int head_check( unsigned long head, int check_layer );
+
+
+static char *shoutamp_version = "shoutamp~: mp3 streaming client v0.5, written by Olaf Matthes";
+
+/* too bad, this needs to be static,
+ handling an array to enable several decoders in pd */
+static MPSTR mps[MAX_DECODERS]; /* decoder buffer */
+int nbinstances = 0;
+
+extern const long freqs[9]; /* the samplerate taken from mpg123 */
+
+
+static t_class *shoutamp_class;
+
+typedef struct _shoutamp
+{
+ t_object x_obj;
+ t_int x_instance; /* instance of the object */
+ t_canvas *x_canvas; /* remember canvas */
+ t_outlet *x_connection;
+ t_clock *x_clock;
+
+ t_float *x_buf; /* audio data buffer (at samplerate of stream) */
+ t_float *x_rsbuf; /* resampled audio data buffer */
+ t_int x_bufsize; /* buffer size in bytes */
+ t_int x_noutlets; /* number of audio outlets */
+ t_sample *(x_outvec[MAXSTREAMCHANS]); /* audio vectors */
+ t_int x_vecsize; /* vector size for transfers */
+ t_int x_state; /* opened, running, or idle */
+ char x_out[DECODE_PACKET_SIZE]; /* buffer for decoded PCM samples */
+
+ /* parameters to communicate with subthread */
+ t_int x_requestcode; /* pending request from parent to I/O thread */
+ t_int x_connecterror; /* slot for "errno" return */
+ t_int x_streamchannels; /* number of channels in Ogg mpg123 bitstream */
+ t_int x_streamrate; /* sample rate of stream */
+
+ /* buffer stuff */
+ t_int x_fifosize; /* buffer size appropriately rounded down */
+ t_int x_fifohead; /* index of next byte to get from file */
+ t_int x_fifotail; /* index of next byte the ugen will read */
+ t_int x_fifobytes; /* number of bytes available in buffer */
+ t_int x_eof; /* true if ogg stream has ended */
+ t_int x_sigcountdown; /* counter for signalling child for more data */
+ t_int x_sigperiod; /* number of ticks per signal */
+ t_int x_siginterval; /* number of times per buffer (depends on data rate) */
+
+ /* mpg123 related stuff */
+ t_int x_eos; /* end of stream */
+ t_int x_bitrateindex; /* bitrate index for each frame, might change dynamically */
+ t_int x_packetsize; /* size of the packets */
+ char x_mp3buffer[READSIZE]; /* input buffer for mpg123 */
+ t_int x_mp3inpos; /* writing position in mp3 stream buffer */
+ t_int x_mpg123; /* info about encoder status */
+ t_int x_verbose; /* turn mpg123 error printout on or off */
+
+
+ t_int x_connectstate; /* indicates the state of socket connection */
+ t_int x_fd; /* the socket number */
+ t_int x_graphic; /* indicates if we show a graphic bar */
+ t_int x_resample; /* indicates if we need to resample signal (1 = no resampling) */
+ t_int x_recover; /* indicate how to behave on buffer underruns */
+ t_int x_disconnect; /* indicates that user want's to disconnect */
+ t_int x_samplerate; /* Pd's sample rate */
+ t_int x_bitrate;
+
+ /* server stuff */
+ char *x_hostname; /* name or IP of host to connect to */
+ char *x_mountpoint; /* mountpoint of ogg-bitstream */
+ t_int x_port; /* port number on which the connection is made */
+
+ /* tread stuff */
+ pthread_mutex_t x_mutex;
+ pthread_cond_t x_requestcondition;
+ pthread_cond_t x_answercondition;
+ pthread_t x_childthread;
+} t_shoutamp;
+
+ /* some prototypes */
+static void shoutamp_child_disconnect(t_int fd);
+
+ /* check if we can read from socket */
+static int shoutamp_check_for_data(t_int sock)
+{
+
+ fd_set set;
+ struct timeval tv;
+ t_int ret;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 20000;
+ FD_ZERO(&set);
+ FD_SET(sock, &set);
+ ret = select(sock + 1, &set, NULL, NULL, &tv);
+ if (ret > 0)
+ return 1;
+ return 0;
+}
+
+ /* receive 'size' bytes from server */
+static int shoutamp_child_receive(int fd, char *rcvbuffer, int size)
+{
+ int ret = -1;
+ int i;
+
+ ret = recv(fd, rcvbuffer, size, 0);
+ if(ret < 0)
+ {
+ post("shoutamp~: receive error" );
+ }
+ return ret;
+}
+
+static int strip_shout_header(char *head, int n)
+{
+ int i;
+ for (i = 0; i < (n - 2); i++)
+ {
+ if (head[i] == 10 && head[i + 1] == 13)
+ break;
+ if (head[i] == '\n' && head[i + 1] == '\n')
+ break;
+ }
+ head[i + 1] = '\0';
+ return n - (i + 1);
+}
+
+static int strip_ice_header(char *head, int n)
+{
+ int i;
+ for (i = 0; i < (n - 2); i++)
+ {
+ if ((head[i] == '\n') && (head[i + 1] == '\n'))
+ break;
+ }
+ head[i + 1] = '\0';
+ return n - (i + 1);
+}
+
+ /* mpg123 decoder setup */
+static int shoutamp_mpg123_init(t_shoutamp *x, int fd)
+{
+ int offset, bytes;
+ float resample = 0;
+ struct frame hframe;
+ unsigned int a,b,c,d;
+ unsigned long cheader;
+
+ InitMP3(&mps[x->x_instance]);
+
+ /* receive some bytes and check them */
+ bytes = shoutamp_child_receive(fd, x->x_mp3buffer, READ);
+ if(bytes < 0)
+ {
+ return (bytes);
+ }
+
+ offset=0;
+ /* search for an header to check dynamic bitrate */
+ while(offset < READ - 4)
+ {
+ /* decode first 4 bytes as the header */
+ a = *((unsigned char*)x->x_mp3buffer+offset);
+ b = *((unsigned char*)x->x_mp3buffer+offset+1);
+ c = *((unsigned char*)x->x_mp3buffer+offset+2);
+ d = *((unsigned char*)x->x_mp3buffer+offset+3);
+
+ cheader = 0;
+ cheader = a;
+ cheader <<= 8;
+ cheader |= b;
+ cheader <<= 8;
+ cheader |= c;
+ cheader <<= 8;
+ cheader |= d;
+ if ( head_check( cheader, 0 ) )
+ {
+ decode_header( &hframe, cheader );
+ print_header_compact( &hframe );
+ x->x_packetsize = hframe.framesize+sizeof(unsigned long);
+ x->x_streamchannels = hframe.stereo; /* how many channels ? */
+ if ( hframe.framesize == 0 )
+ {
+ post( "shoutamp~: weird header ( frame size = 0 ) .... ignored" );
+ offset++;
+ continue;
+ }
+ // when the bitrate change, reinit decoder
+ x->x_bitrateindex = hframe.bitrate_index;
+ /* setting for resampling */
+ x->x_streamrate = freqs[hframe.sampling_frequency];
+ if(x->x_streamrate != x->x_samplerate)
+ {
+ if(x->x_streamrate > 0)
+ resample = (float)x->x_samplerate / (float)x->x_streamrate;
+ if(resample == 1.0)x->x_resample = 1;
+ else if(resample == 2.0)x->x_resample = 2;
+ else if(resample == 4.0)x->x_resample = 4;
+ else if(resample == 3.0)x->x_resample = 3;
+ else x->x_resample = -1;
+ if(x->x_resample==-1)
+ {
+ post("shoutamp~: resampling from %ld Hz to %d not supported",
+ x->x_streamrate, x->x_samplerate );
+ return -1;
+ }
+ else post("shoutamp~: resampling from %ld Hz to %d Hz", x->x_streamrate, x->x_samplerate );
+ }
+ else x->x_resample = 1;
+ break;
+ }
+ offset++;
+ }
+ x->x_mp3inpos = READ - offset; /* throw away first few bytes */
+ memcpy(x->x_mp3buffer, x->x_mp3buffer+offset, x->x_mp3inpos); /* move start of header to start of buffer */
+
+ /* read next 4k of data */
+ bytes = shoutamp_child_receive(fd, x->x_mp3buffer+x->x_mp3inpos, READ);
+ if(bytes < 0)
+ {
+ x->x_eos = 1; /* indicate end of stream */
+ return (bytes);
+ }
+ else x->x_mp3inpos += bytes;
+
+ x->x_mpg123 = 1; /* indicate that mpg123 is OK */
+ return (1);
+}
+
+ /* deinit the mpg123 decoder */
+static void shoutamp_mpg123_deinit(t_shoutamp *x)
+{
+ if(x->x_mpg123)
+ {
+ x->x_mpg123 = 0;
+ ExitMP3(&mps[x->x_instance]);
+ post("shoutamp~: decoder deinitialised");
+ }
+}
+
+ /* decode ogg/mpg123 and receive new data */
+static int shoutamp_decode_input(t_shoutamp *x, float *buf, int fifohead, int fifosize, int fd)
+{
+ int i, j, ch, ret;
+
+ signed short int *p = (signed short int *) x->x_out;
+ int samples = 0;/* number of samples returned by decoder at each block! */
+ int channels; /* number of channels */
+ int n = 0; /* total number of samples returned by decoder at this call */
+ int bytes; /* number of bytes submitted to decoder */
+ int position = fifohead;
+ int pbytes;
+ struct frame hframe;
+ unsigned int a,b,c,d;
+ unsigned long cheader;
+ int offset;
+
+ while(x->x_mp3inpos > READ) /* at least two packets in buffer */
+ {
+ offset = 0;
+ /* search for an header to check dynamic bitrate */
+ while(offset < x->x_mp3inpos - 4)
+ {
+ /* decode first 4 bytes as the header */
+ a = *((unsigned char*)x->x_mp3buffer+offset);
+ b = *((unsigned char*)x->x_mp3buffer+offset+1);
+ c = *((unsigned char*)x->x_mp3buffer+offset+2);
+ d = *((unsigned char*)x->x_mp3buffer+offset+3);
+
+ cheader = 0;
+ cheader = a;
+ cheader <<= 8;
+ cheader |= b;
+ cheader <<= 8;
+ cheader |= c;
+ cheader <<= 8;
+ cheader |= d;
+ if(head_check( cheader, 0 )) /* is this a header ? */
+ {
+ decode_header(&hframe, cheader);
+ x->x_packetsize = hframe.framesize + (int)sizeof(unsigned long);
+ if(hframe.framesize == 0)
+ {
+ post( "shoutamp~: weird header ( frame size = 0 ) .... ignored" );
+ offset++;
+ continue;
+ }
+ // print_header_compact( &hframe );
+ /* when the bitrate changes, reinit decoder */
+ if(x->x_bitrateindex != hframe.bitrate_index)
+ {
+ post("shoutamp~: reinitialize decoder (bitrate changed)");
+ ExitMP3(&mps[x->x_instance]);
+ InitMP3(&mps[x->x_instance]);
+ x->x_bitrateindex = hframe.bitrate_index;
+ }
+ break;
+ }
+ offset++;
+ }
+
+ ret = decodeMP3(&mps[x->x_instance], (unsigned char*)(x->x_mp3buffer+offset),
+ x->x_packetsize, (char *)p, sizeof(x->x_out), &pbytes);
+
+ channels = mps[x->x_instance].fr.stereo; /* get current number of channels */
+
+ switch (ret)
+ {
+ case MP3_OK:
+ switch (channels)
+ {
+ case 1:
+ case 2:
+ samples = ((channels==1)?pbytes >> 1 : pbytes >> 2);
+ /* copy decoded PCM into our output buffer */
+ pthread_mutex_lock(&x->x_mutex);
+ for(j = 0; j < samples; j++)
+ {
+ for(ch = 0; ch < channels; ch++)
+ {
+ buf[position] = ((t_float)(*p++))/32767.0;
+ position++;
+ }
+ if (position >= fifosize) /* check for buffer boundaries */
+ position = 0;
+ }
+ pthread_mutex_unlock(&x->x_mutex);
+ n += (samples * channels); /* count bytes we wrote to buffer */
+ p -= (samples * channels); /* point to start of buffer again */
+
+ /* move unused data to start of buffer ("roll buffer") */
+ if(x->x_mp3inpos > x->x_packetsize)
+ {
+ x->x_mp3inpos -= x->x_packetsize + offset; /* move one frame */
+ memcpy( (void *)(x->x_mp3buffer),
+ (void *)(x->x_mp3buffer+x->x_packetsize + offset),
+ x->x_mp3inpos);
+ }
+ else /* sorry, it will be ignored */
+ {
+ /* error( "shoutamp~: incomplete frame...ignored");
+ x->x_mp3inpos = 0; */
+ goto receive;
+ }
+ break;
+ default:
+ samples = 0; /* nothing added to buffer */
+ break;
+ }
+ break;
+
+ case MP3_NEED_MORE:
+ if(mps[x->x_instance].framesize == 0 && mps[x->x_instance].fsizeold != 0)
+ {
+ post( "shoutamp~: decoding done");
+ }
+ else
+ {
+ post( "shoutamp~: retry lame decoding (more data needed)" );
+ goto receive;
+ }
+ break;
+
+ case MP3_ERR:
+ post( "shoutamp~: lame decoding failed" );
+ return (-1);
+ break;
+
+ }
+ }
+
+receive:
+ /* read data from socket */
+ if(x->x_mp3inpos < READSIZE - READ) /* any space in buffer ? */
+ {
+ if(!x->x_eos) /* read data from input */
+ {
+ /* read next 4k of data out of buffer */
+ bytes = shoutamp_child_receive(fd, x->x_mp3buffer+x->x_mp3inpos, READ);
+ if(bytes < 0)
+ {
+ x->x_eos = 1; /* indicate end of stream */
+ return (bytes);
+ }
+ else x->x_mp3inpos += bytes;
+ }
+ else /* we read through all the file... */
+ { /* will have to reinit decoder for new file */
+ post("shoutamp~: end of stream detected");
+ return (0);
+ }
+ }
+ else /* no? - drop the whole thing */
+ {
+ post("shoutamp~: mp3 stream buffer dropped");
+ x->x_mp3inpos = 0;
+ shoutamp_mpg123_init(x, fd);
+ }
+ // post("n = %d", n);
+ return (n);
+}
+
+ /* connect to shoutcast server */
+static int shoutamp_child_connect(char *hostname, char *mountpoint, t_int portno)
+{
+ struct sockaddr_in server;
+ struct hostent *hp;
+
+ /* variables used for communication with server */
+ char *sptr = NULL;
+ char request[STRBUF_SIZE]; /* string to be send to server */
+ char *url; /* used for relocation */
+ char* bcname; /* name of broadcast */
+ char* bcurl; /* url of broadcast */
+ char* bcgenre; /* genre of broadcast */
+ char* bcaim; /* aim of broadcast */
+ fd_set fdset;
+ struct timeval tv;
+ t_int sockfd; /* socket to server */
+ t_int relocate, numrelocs = 0;
+ t_int i, ret, rest, nanswers=0;
+ char *cpoint = NULL;
+ t_int eof = 0;
+
+ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (sockfd < 0)
+ {
+ error("shoutamp~: internal error while attempting to open socket");
+ return (-1);
+ }
+
+ /* connect socket using hostname provided in command line */
+ server.sin_family = AF_INET;
+ hp = gethostbyname(hostname);
+ if (hp == 0)
+ {
+ post("shoutamp~: bad host?");
+ sys_closesocket(sockfd);
+ return (-1);
+ }
+ memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length);
+
+ /* assign client port number */
+ server.sin_port = htons((unsigned short)portno);
+
+ /* check mountpoint :: SHOUcast does not support mountpoints */
+ if(strstr(mountpoint, "listen.pls") || strstr(mountpoint, "/"))
+ {
+ mountpoint = ""; /* the '/' is not part of the mountpoint !!! */
+ }
+
+ /* try to connect. */
+ post("shoutamp~: connecting to http://%s:%d/%s", hostname, portno, mountpoint);
+ if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0)
+ {
+ error("shoutamp~: connection failed!\n");
+ sys_closesocket(sockfd);
+ return (-1);
+ }
+
+ /* sheck if we can read/write from/to the socket */
+ FD_ZERO( &fdset);
+ FD_SET( sockfd, &fdset);
+ tv.tv_sec = 0; /* seconds */
+ tv.tv_usec = 500; /* microseconds */
+
+ ret = select(sockfd + 1, &fdset, NULL, NULL, &tv);
+ if(ret != 0)
+ {
+ error("shoutamp~: can not read from socket");
+ sys_closesocket(sockfd);
+ return (-1);
+ }
+
+ /* build up stuff we need to send to server */
+ sprintf(request, "GET /%s HTTP/1.0 \r\nHost: %s\r\nUser-Agent: shoutamp~ 0.5\r\nAccept: */*\r\n\r\n",
+ mountpoint, hostname);
+
+ if(send(sockfd, request, strlen(request), 0) < 0) /* say hello to server */
+ {
+ post("shoutamp~: could not contact server... ");
+#ifndef NT
+ perror("send");
+#endif
+ return (-1);
+ }
+
+ relocate = FALSE;
+
+ if( recv(sockfd, request, STRBUF_SIZE, 0)<0)
+ {
+ error("shoutamp~: no response from server");
+#ifndef NT
+ perror( "recv" );
+#endif
+ shoutamp_child_disconnect(sockfd);
+ return (-1);
+ }
+
+ /* time to parse content of the response... */
+ if(strstr(request, "audio/x-scpls") ) /* SHOUTcast playlist */
+ {
+ /* LATER: parse playlist and retry */
+ post("shoutamp~: SHOUTcast server returned a playlist, quitting");
+ shoutamp_child_disconnect(sockfd);
+ return (-1);
+ }
+ if(strstr(request, "HTTP")) /* seems to be IceCast server */
+ {
+ strip_ice_header(request, STRBUF_SIZE);
+ if(sptr = strstr(request, "302"))
+ {
+ cpoint = strstr(request, "Location:");
+ url = STRDUP(cpoint + 10);
+ post("shoutamp~: relocation requested, try \"%s\" instead", url);
+ shoutamp_child_disconnect(sockfd);
+ // return (shoutamp_connect_url(x, gensym(url)));
+ relocate = TRUE;
+ return (-1);
+ }
+ if(!(sptr = strstr(request, "200")) && !relocate )
+ {
+ error("shoutamp~: cannot connect to the (default) stream");
+ shoutamp_child_disconnect(sockfd);
+ return (-1);
+ }
+
+ post("shoutamp~: IceCast server detected");
+
+ /* eventually read the rest of server's announcement */
+ while((!(cpoint = strstr(request, "x-audiocast-public") ) ||
+ !(cpoint = strstr(request, "x-audiocast-mount") ) ||
+ !(cpoint = strstr(request, "x-audiocast-server-url") ) ||
+ !(cpoint = strstr(request, "x-audiocast-location") ) ||
+ !(cpoint = strstr(request, "x-audiocast-admin") ) ||
+ !(cpoint = strstr(request, "x-audiocast-name") ) ||
+ !(cpoint = strstr(request, "x-audiocast-genre") ) ||
+ !(cpoint = strstr(request, "x-audiocast-url") ) ||
+ !(cpoint = strstr(request, "x-audiocast-public") ) ||
+ !(cpoint = strstr(request, "x-audiocast-bitrate") ) ) &&
+ ( ret < STRBUF_SIZE ) )
+ {
+ if((rest = recv(sockfd, request+ret, STRBUF_SIZE-ret, 0) ) < 0 )
+ {
+ error("shoutamp~: no response from server");
+#ifndef NT
+ perror("recv");
+#endif
+ shoutamp_child_disconnect(sockfd);
+ return(-1);
+ }
+ ret += rest;
+ }
+
+ // post("shoutamp~: server's header : %s", request );
+
+ /* check what we got */
+ if(cpoint = strstr(request, "x-audiocast-mount:"))
+ {
+ mountpoint = STRDUP(cpoint + 18);
+ for ( i=0; i<(int)strlen(mountpoint); i++ )
+ {
+ if(mountpoint[i] == '\n' )
+ {
+ mountpoint[i] = '\0';
+ break;
+ }
+ }
+ post(" mountpoint: %s", mountpoint);
+ }
+ if(cpoint = strstr(request, "x-audiocast-server-url:"))
+ {
+ sptr = STRDUP( cpoint + 24);
+ for ( i=0; i<(int)strlen(sptr); i++ )
+ {
+ if ( sptr[i] == '\n' )
+ {
+ sptr[i] = '\0';
+ break;
+ }
+ }
+ post(" server-url: %s", sptr);
+ }
+ if(cpoint = strstr(request, "x-audiocast-location:"))
+ {
+ sptr = STRDUP( cpoint + 22);
+ for ( i=0; i<(int)strlen(sptr); i++ )
+ {
+ if ( sptr[i] == '\n' )
+ {
+ sptr[i] = '\0';
+ break;
+ }
+ }
+ post(" location: %s", sptr);
+ }
+ if(cpoint = strstr(request, "x-audiocast-admin:"))
+ {
+ sptr = STRDUP( cpoint + 19);
+ for ( i=0; i<(int)strlen(sptr); i++ )
+ {
+ if ( sptr[i] == '\n' )
+ {
+ sptr[i] = '\0';
+ break;
+ }
+ }
+ post(" admin: %s", sptr);
+ }
+ if(cpoint = strstr(request, "x-audiocast-name:"))
+ {
+ bcname = STRDUP( cpoint + 17);
+ for ( i=0; i<(int)strlen(bcname); i++ )
+ {
+ if ( bcname[i] == '\n' )
+ {
+ bcname[i] = '\0';
+ break;
+ }
+ }
+ post(" name: %s", bcname);
+ }
+ if(cpoint = strstr(request, "x-audiocast-genre:"))
+ {
+ bcgenre = STRDUP( cpoint + 18);
+ for ( i=0; i<(int)strlen(bcgenre); i++ )
+ {
+ if ( bcgenre[i] == '\n' )
+ {
+ bcgenre[i] = '\0';
+ break;
+ }
+ }
+ post(" genre: %s", bcgenre);
+ }
+ if(cpoint = strstr(request, "x-audiocast-url:"))
+ {
+ bcurl = STRDUP( cpoint + 16);
+ for ( i=0; i<(int)strlen(bcurl); i++ )
+ {
+ if ( bcurl[i] == '\n' )
+ {
+ bcurl[i] = '\0';
+ break;
+ }
+ }
+ post(" url: %s", bcurl);
+ }
+ if(cpoint = strstr(request, "x-audiocast-public:1"))
+ {
+ post(" broadcast is public");
+ }
+ else if(cpoint = strstr(request, "x-audiocast-public:0"))
+ {
+ post(" broadcast is NOT public");
+ }
+ if(cpoint = strstr(request, "x-audiocast-bitrate:"))
+ {
+ sptr = STRDUP( cpoint + 20);
+ for ( i=0; i<(int)strlen(sptr); i++ )
+ {
+ if ( sptr[i] == '\n' )
+ {
+ sptr[i] = '\0';
+ break;
+ }
+ }
+ post(" bitrate: %s", sptr);
+ }
+ if(cpoint = strstr(request, "x-audiocast-udpport:"))
+ {
+ post("shoutamp~: sorry, server wants UDP connection!");
+ return (-1);
+ }
+ }
+ else /* it is a SHOUTcast server */
+ {
+ strip_shout_header (request, STRBUF_SIZE);
+ post("shoutamp~: SHOUTcast server detected");
+ if(strncmp(request, "ICY 401", 7) == 0)
+ {
+ post("shoutamp~: ICY 401 Service Unavailable");
+ return (-1);
+ }
+ if(strncmp(request, "ICY 200 OK", 10) == 0)
+ {
+ /* recv and decode info about broadcast line by line */
+ post("shoutamp~: connecting to stream...");
+ i = ret;
+ /* break response down to single lines */
+ while(i < STRBUF_SIZE - 1)
+ {
+ for(rest = ret; rest < STRBUF_SIZE - 1; rest++)
+ {
+ if(recv(sockfd, request+rest, 1, 0)!=1)
+ break;
+ if (request[rest] == '\n')
+ break;
+ if (request[rest] == '\r')
+ break;
+ i++;
+ }
+ request[rest] = '\0';
+
+ /* check what we got */
+ if(!strncmp(request, "icy-name:", 9))
+ {
+ bcname = STRDUP(request + 9);
+ post(" name: %s", bcname);
+ }
+ else if(!strncmp(request, "x-audiocast-name:", 17))
+ {
+ bcname = STRDUP(request + 17);
+ post(" name: %s", bcname);
+ }
+ if(!strncmp(request, "icy-genre:", 9))
+ {
+ bcgenre = STRDUP(request + 10);
+ post(" genre: %s", bcgenre);
+ }
+ if(!strncmp(request, "icy-aim:", 7))
+ {
+ bcaim = STRDUP(request + 8);
+ post(" aim: %s", bcaim);
+ }
+ if(!strncmp(request, "icy-url:", 7))
+ {
+ bcurl = STRDUP(request + 8);
+ post(" url: %s", bcurl);
+ }
+ if(!strncmp(request, "icy-pub:1", 8))
+ {
+ post(" broadcast is public");
+ }
+ else if(!strncmp(request, "icy-pub:0", 8))
+ {
+ post(" broadcast is NOT public");
+ }
+ if(!strncmp(request, "icy-br:", 6))
+ {
+ sptr = STRDUP(request + 7);
+ post(" bitrate: %s", sptr);
+ }
+ if(!strncmp(request, "x-audiocast-udpport:", 20))
+ {
+ post("shoutamp~: sorry, server wants UDP connection!");
+ return (-1);
+ }
+
+ }
+ }
+ else
+ {
+ post("shoutamp~: unknown response from server");
+ return (-1);
+ }
+ relocate = FALSE;
+ }
+ if(relocate)
+ {
+ error("shoutamp~: too many HTTP relocations");
+ return (-1);
+ }
+ post("shoutamp~: connected to http://%s:%d/%s", hp->h_name, portno, mountpoint);
+
+ return (sockfd);
+}
+
+
+static void shoutamp_child_dographics(t_shoutamp *x)
+{
+ /* do graphics stuff :: create rectangle */
+ if ( x->x_graphic && glist_isvisible( x->x_canvas ) )
+ {
+ sys_vgui(".x%x.c create rectangle %d %d %d %d -fill lightblue -tags %xPBAR\n",
+ x->x_canvas, x->x_obj.te_xpix, x->x_obj.te_ypix-OBJHEIGHT-1,
+ x->x_obj.te_xpix + OBJWIDTH, x->x_obj.te_ypix - 1, x );
+ }
+}
+
+static void shoutamp_child_updategraphics(t_shoutamp *x)
+{
+ /* update buffer status display */
+ if(x->x_graphic && glist_isvisible(x->x_canvas))
+ {
+ /* update graphical read status */
+ char color[32];
+
+ sys_vgui(".x%x.c delete rectangle %xSTATUS\n", x->x_canvas, x);
+ if(x->x_fifobytes < (x->x_fifosize / 8))
+ {
+ strcpy(color, "red");
+ }
+ else
+ {
+ strcpy(color, "lightgreen");
+ }
+ sys_vgui(".x%x.c create rectangle %d %d %d %d -fill %s -tags %xSTATUS\n",
+ x->x_canvas, x->x_obj.te_xpix, x->x_obj.te_ypix-OBJHEIGHT-1,
+ x->x_obj.te_xpix+((x->x_fifobytes*OBJWIDTH)/x->x_fifosize),
+ x->x_obj.te_ypix - 1, color, x);
+ }
+}
+static void shoutamp_child_delgraphics(t_shoutamp *x)
+{
+ if(x->x_graphic) /* delete graphics */
+ {
+ sys_vgui(".x%x.c delete rectangle %xPBAR\n", x->x_canvas, x );
+ sys_vgui(".x%x.c delete rectangle %xSTATUS\n", x->x_canvas, x );
+ }
+}
+
+static void shoutamp_child_disconnect(t_int fd)
+{
+ sys_closesocket(fd);
+ post("shoutamp~: connection closed");
+}
+
+/************** the child thread which performs data I/O ***********/
+
+#if 0 /* set this to get debugging output */
+static void pute(char *s) /* debug routine */
+{
+ write(2, s, strlen(s));
+}
+#else
+#define pute(x)
+#endif
+
+#if 1
+#define shoutamp_cond_wait pthread_cond_wait
+#define shoutamp_cond_signal pthread_cond_signal
+#else
+#include <sys/time.h> /* debugging version... */
+#include <sys/types.h>
+static void shoutamp_fakewait(pthread_mutex_t *b)
+{
+ struct timeval timout;
+ timout.tv_sec = 0;
+ timout.tv_usec = 1000000;
+ pthread_mutex_unlock(b);
+ select(0, 0, 0, 0, &timout);
+ pthread_mutex_lock(b);
+}
+
+void shoutamp_banana( void)
+{
+ struct timeval timout;
+ timout.tv_sec = 0;
+ timout.tv_usec = 200000;
+ pute("banana1\n");
+ select(0, 0, 0, 0, &timout);
+ pute("banana2\n");
+}
+
+
+#define shoutamp_cond_wait(a,b) shoutamp_fakewait(b)
+#define shoutamp_cond_signal(a)
+#endif
+
+static void *shoutamp_child_main(void *zz)
+{
+ t_shoutamp *x = zz;
+ pute("1\n");
+ pthread_mutex_lock(&x->x_mutex);
+ while (1)
+ {
+ int fd, fifohead;
+ char *buffer; /* mp3 data */
+ float *buf; /* encoded PCM floats */
+ pute("0\n");
+ if (x->x_requestcode == REQUEST_NOTHING)
+ {
+ pute("wait 2\n");
+ shoutamp_cond_signal(&x->x_answercondition);
+ shoutamp_cond_wait(&x->x_requestcondition, &x->x_mutex);
+ pute("3\n");
+ }
+ // connect to SHOUTcast or Icecast server
+ else if (x->x_requestcode == REQUEST_CONNECT)
+ {
+ char boo[80];
+ int sysrtn, wantbytes;
+
+ /* copy connect stuff out of the data structure so we can
+ relinquish the mutex while we're in oggcast_child_connect(). */
+ char *hostname = x->x_hostname;
+ char *mountpoint = x->x_mountpoint;
+ t_int portno = x->x_port;
+ x->x_disconnect = 0;
+ /* alter the request code so that an ensuing "open" will get
+ noticed. */
+ pute("4\n");
+ x->x_requestcode = REQUEST_BUSY;
+ x->x_connecterror = 0;
+
+ /* if there's already a connection open, close it */
+ if (x->x_fd >= 0)
+ {
+ fd = x->x_fd;
+ pthread_mutex_unlock(&x->x_mutex);
+ shoutamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ x->x_fd = -1;
+ if (x->x_requestcode != REQUEST_BUSY)
+ goto lost;
+ }
+ /* open the socket with the mutex unlocked */
+ pthread_mutex_unlock(&x->x_mutex);
+ fd = shoutamp_child_connect(hostname, mountpoint, portno);
+ pthread_mutex_lock(&x->x_mutex);
+ pute("5\n");
+ /* copy back into the instance structure. */
+ x->x_connectstate = 1;
+ clock_delay(x->x_clock, 0);
+ x->x_fd = fd;
+ if (fd < 0)
+ {
+ x->x_connecterror = fd;
+ x->x_eof = 1;
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ pute("connect failed\n");
+ goto lost;
+ }
+ else
+ {
+ /* initialise the decoder */
+ if(shoutamp_mpg123_init(x, fd) != -1)
+ {
+ post("shoutamp~: decoder initialised");
+ shoutamp_child_dographics(x);
+ }
+ else
+ {
+ post("shoutamp~: could not init decoder");
+ shoutamp_child_disconnect(fd);
+ post("shoutamp~: connection closed due to bitstream error");
+ x->x_disconnect = 1;
+ x->x_fd = -1;
+ x->x_eof = 1;
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ pute("initialisation failed\n");
+ goto lost;
+ }
+ }
+ /* check if another request has been made; if so, field it */
+ if (x->x_requestcode != REQUEST_BUSY)
+ goto lost;
+ pute("6\n");
+ x->x_fifohead = fifohead = 0;
+ /* set fifosize from bufsize. fifosize must be a
+ multiple of the number of bytes eaten for each DSP
+ tick. We pessimistically assume MAXVECSIZE samples
+ per tick since that could change. There could be a
+ problem here if the vector size increases while a
+ stream is being played... */
+ x->x_fifosize = x->x_bufsize - (x->x_bufsize %
+ (x->x_streamchannels * 2));
+ /* arrange for the "request" condition to be signalled x->x_siginterval
+ times per buffer */
+ sprintf(boo, "fifosize %d\n",
+ x->x_fifosize);
+ pute(boo);
+ x->x_sigcountdown = x->x_sigperiod = (x->x_fifosize / (x->x_siginterval * x->x_streamchannels * x->x_vecsize));
+
+ /* in a loop, wait for the fifo to get hungry and feed it */
+ while (x->x_requestcode == REQUEST_BUSY)
+ {
+ int fifosize = x->x_fifosize;
+ buf = x->x_buf;
+ pute("77\n");
+ if (x->x_eof)
+ break;
+ /* try to get new data from decoder whenever
+ there is some space at end of buffer */
+ if(x->x_fifobytes < fifosize - DECODE_PACKET_SIZE)
+ {
+ sprintf(boo, "head %d, tail %d\n", x->x_fifohead, x->x_fifotail);
+ pute(boo);
+
+ /* we pass x on to the routine since we need the mpg123 stuff
+ to be presend. all other values should not be changed because
+ mutex is unlocked ! */
+ pute("decode... ");
+ pthread_mutex_unlock(&x->x_mutex);
+ sysrtn = shoutamp_decode_input(x, buf, fifohead, fifosize, fd);
+ shoutamp_child_updategraphics(x);
+ pthread_mutex_lock(&x->x_mutex);
+ if (x->x_requestcode != REQUEST_BUSY)
+ break;
+ if (sysrtn == 0)
+ {
+ if (x->x_eos && !x->x_disconnect) /* got end of stream */
+ {
+ pute("end of stream\n");
+ shoutamp_mpg123_deinit(x);
+ if(shoutamp_mpg123_init(x, fd) == -1) /* reinit stream */
+ {
+ x->x_state = STATE_IDLE;
+ x->x_disconnect = 1;
+ goto quit;
+ }
+ }
+ else if (x->x_eos && x->x_disconnect) /* we're disconnecting */
+ {
+ x->x_state = STATE_IDLE;
+ pute("end of stream: disconnecting\n");
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ break; /* go to disconnect */
+ }
+ }
+ else if (sysrtn < 0) /* got any other error from decoder */
+ {
+ pute("connecterror\n");
+ x->x_connecterror = sysrtn;
+ x->x_state = STATE_IDLE;
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ break;
+ }
+ x->x_fifohead = (fifohead + sysrtn) % fifosize;
+ x->x_fifobytes += sysrtn;
+ sprintf(boo, "after: head %d, tail %d\n",
+ x->x_fifohead, x->x_fifotail);
+ pute(boo);
+ /* check wether the buffer is filled enough to start streaming */
+ }
+ else /* there is enough data in the buffer :: do nothing */
+ {
+ x->x_state = STATE_STREAM;
+ pute("wait 7...\n");
+ shoutamp_cond_signal(&x->x_answercondition);
+ shoutamp_cond_wait(&x->x_requestcondition, &x->x_mutex);
+ pute("7 done\n");
+ continue;
+ }
+ pute("8\n");
+ fd = x->x_fd;
+ buf = x->x_buf;
+ fifohead = x->x_fifohead;
+ pthread_mutex_unlock(&x->x_mutex);
+
+ /* signal parent in case it's waiting for data */
+ shoutamp_cond_signal(&x->x_answercondition);
+ }
+
+lost:
+
+ if (x->x_requestcode == REQUEST_BUSY)
+ x->x_requestcode = REQUEST_NOTHING;
+ /* fell out of read loop: close connection if necessary,
+ set EOF and signal once more */
+ if (x->x_fd >= 0)
+ {
+ fd = x->x_fd;
+ pthread_mutex_unlock(&x->x_mutex);
+ shoutamp_mpg123_deinit(x);
+ shoutamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ x->x_fd = -1;
+ shoutamp_child_delgraphics(x);
+ }
+ shoutamp_cond_signal(&x->x_answercondition);
+
+ }
+ /* reconnect to server */
+ else if (x->x_requestcode == REQUEST_RECONNECT)
+ {
+ if (x->x_fd >= 0)
+ {
+ fd = x->x_fd;
+ pthread_mutex_unlock(&x->x_mutex);
+ shoutamp_mpg123_deinit(x);
+ shoutamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ shoutamp_child_delgraphics(x);
+ x->x_fd = -1;
+ }
+ /* connect again */
+ x->x_requestcode = REQUEST_CONNECT;
+
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ shoutamp_cond_signal(&x->x_answercondition);
+ }
+ /* close connection to server (disconnect) */
+ else if (x->x_requestcode == REQUEST_CLOSE)
+ {
+quit:
+ if (x->x_fd >= 0)
+ {
+ fd = x->x_fd;
+ pthread_mutex_unlock(&x->x_mutex);
+ shoutamp_mpg123_deinit(x);
+ shoutamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ shoutamp_child_delgraphics(x);
+ x->x_fd = -1;
+ }
+ if (x->x_requestcode == REQUEST_CLOSE)
+ x->x_requestcode = REQUEST_NOTHING;
+ else if (x->x_requestcode == REQUEST_BUSY)
+ x->x_requestcode = REQUEST_NOTHING;
+ else if (x->x_requestcode == REQUEST_CONNECT)
+ x->x_requestcode = REQUEST_NOTHING;
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ shoutamp_cond_signal(&x->x_answercondition);
+ }
+ // quit everything
+ else if (x->x_requestcode == REQUEST_QUIT)
+ {
+ if (x->x_fd >= 0)
+ {
+ fd = x->x_fd;
+ pthread_mutex_unlock(&x->x_mutex);
+ shoutamp_mpg123_deinit(x);
+ shoutamp_child_disconnect(fd);
+ pthread_mutex_lock(&x->x_mutex);
+ shoutamp_child_delgraphics(x);
+ x->x_fd = -1;
+ }
+ x->x_connectstate = 0;
+ clock_delay(x->x_clock, 0);
+ x->x_requestcode = REQUEST_NOTHING;
+ shoutamp_cond_signal(&x->x_answercondition);
+ break;
+ }
+ else
+ {
+ pute("13\n");
+ }
+ }
+ pute("thread exit\n");
+ pthread_mutex_unlock(&x->x_mutex);
+ return (0);
+}
+
+/******** the object proper runs in the calling (parent) thread ****/
+
+static void shoutamp_tick(t_shoutamp *x)
+{
+ outlet_float(x->x_connection, x->x_connectstate);
+}
+
+static void *shoutamp_new(t_floatarg fdographics, t_floatarg fnchannels, t_floatarg fbufsize)
+{
+ t_shoutamp *x;
+ int nchannels = fnchannels, bufsize = fbufsize * 1024, i;
+ float *buf;
+
+ if (nchannels < 1)
+ nchannels = 2; /* two channels as default */
+ else if (nchannels > MAXSTREAMCHANS)
+ nchannels = MAXSTREAMCHANS;
+ /* check / set buffer size */
+ if (!bufsize) bufsize = DEFBUFPERCHAN * nchannels;
+ else if (bufsize < MINBUFSIZE)
+ bufsize = MINBUFSIZE;
+ else if (bufsize > MAXBUFSIZE)
+ bufsize = MAXBUFSIZE;
+ buf = getbytes(bufsize*sizeof(t_float));
+ if (!buf) return (0);
+
+ x = (t_shoutamp *)pd_new(shoutamp_class);
+
+ if ( nbinstances < MAX_DECODERS )
+ {
+ x->x_instance = nbinstances++;
+ }
+ else
+ {
+ post( "shoutamp~: cannot create more decoders (memory issues), sorry" );
+ return NULL;
+ }
+ /* what happens if blacksize changes ? */
+ x->x_rsbuf = getbytes(sys_getblksize()*sizeof(t_float)*2);
+
+ for (i = 0; i < nchannels; i++)
+ outlet_new(&x->x_obj, gensym("signal"));
+ x->x_noutlets = nchannels;
+ x->x_connection = outlet_new(&x->x_obj, gensym("float"));
+ x->x_clock = clock_new(x, (t_method)shoutamp_tick);
+
+ pthread_mutex_init(&x->x_mutex, 0);
+ pthread_cond_init(&x->x_requestcondition, 0);
+ pthread_cond_init(&x->x_answercondition, 0);
+
+ x->x_vecsize = sys_getblksize();
+ x->x_disconnect = 0;
+ x->x_state = STATE_IDLE;
+ x->x_canvas = canvas_getcurrent();
+ x->x_streamchannels = 2;
+ x->x_fd = -1;
+ x->x_buf = buf;
+ x->x_bufsize = bufsize;
+ x->x_siginterval = 16; /* signal 16 times per buffer */
+ x->x_fifosize = x->x_fifohead = x->x_fifotail = x->x_fifobytes = x->x_requestcode = 0;
+ x->x_verbose = 1;
+
+ x->x_connectstate = 0; /* indicating state of connection */
+
+ x->x_samplerate = x->x_streamrate = sys_getsr();
+ x->x_resample = 0;
+ x->x_mpg123 = 0;
+ x->x_recover = -1; /* just ignore buffer underruns */
+ x->x_mp3inpos = 0;
+
+ /* graphical buffer status display */
+ x->x_graphic = (int)fdographics;
+ x->x_canvas = canvas_getcurrent();
+
+ post(shoutamp_version);
+ post("shoutamp~: set buffer to %dk bytes", bufsize/1024);
+
+ /* start child thread */
+ pthread_create(&x->x_childthread, 0, shoutamp_child_main, x);
+ return (x);
+}
+
+ /* resample audio data */
+static int shoutamp_resample(t_float *indata, int insize, t_float *outdata, int outsize)
+{
+ if(insize == outsize) /* no resampling needed */
+ {
+ memcpy(indata, outdata, insize); /* just copy audio data */
+ }
+
+}
+
+static t_int *shoutamp_perform(t_int *w)
+{
+ t_shoutamp *x = (t_shoutamp *)(w[1]);
+ t_int vecsize = x->x_vecsize, noutlets = x->x_noutlets, i, j, r;
+ t_float *fp;
+ t_float *sp = x->x_buf;
+ t_float *buffer = x->x_rsbuf; /* buffer for resampled data */
+ t_float resample = x->x_resample;
+ t_int skip = (t_int)(1.0 / resample);
+
+ if (x->x_state == STATE_STREAM)
+ {
+ t_int wantbytes, getbytes, havebytes, nchannels, streamchannels = x->x_streamchannels;
+
+ pthread_mutex_lock(&x->x_mutex);
+
+ /* get 'getbytes' bytes from input buffer, convert them to
+ 'wantbytes' which is the number of bytes after resampling */
+ getbytes = streamchannels * vecsize; /* number of bytes we need to get after resampling */
+ wantbytes = (t_float)getbytes / resample; /* we need vecsize bytes per channel */
+ havebytes = x->x_fifobytes;
+
+ /* check for error */
+ if(havebytes < wantbytes)
+ {
+ // post("shoutamp~: found error");
+ if(x->x_connecterror)
+ { /* report error and close connection */
+ pd_error(x, "dsp: error %d", x->x_connecterror);
+ x->x_state = STATE_IDLE;
+ x->x_requestcode = REQUEST_CLOSE;
+ x->x_disconnect = 1;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ if(!x->x_disconnect) /* it's not due to disconnect */
+ {
+ if(x->x_recover == 0) /* disconnect */
+ {
+ x->x_state = STATE_IDLE;
+ x->x_requestcode = REQUEST_CLOSE;
+ x->x_disconnect = 1;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ else if(x->x_recover == 1) /* reconnect */
+ {
+ x->x_state = STATE_IDLE;
+ x->x_requestcode = REQUEST_RECONNECT;
+ x->x_disconnect = 1;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ else /* resume */
+ {
+ x->x_state = STATE_IDLE;
+ x->x_disconnect = 0;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ }
+ goto idle;
+ }
+
+ /* output audio */
+ sp += x->x_fifotail; /* go to actual audio position */
+
+ /* resample if necessary */
+ if (resample > 1.0) /* upsampling */
+ {
+ int parent = vecsize / resample; /* how many samples to read from buffer */
+ for(j = 0; j < parent; j++)
+ {
+ for(i = 0; i < streamchannels; i++)
+ { /* copy same sample several times */
+ for (r = 0; r < resample; r++)
+ *buffer++ = *sp;
+ sp++; /* get next sample from stream */
+ }
+ }
+ }
+ else if (resample < 1.0) /* downsampling */
+ {
+ int parent = vecsize * skip;/* how many samples to read from buffer */
+ for(j = 0; j < parent; j++)
+ {
+ for(i = 0; i < streamchannels; i++)
+ {
+ *buffer++ = *sp; /* get one sample */
+ sp += skip; /* skip next few samples in stream */
+ }
+ }
+ }
+ else if (resample == 1.0) /* no resampling */
+ { /* copy without any changes */
+ for(i = 0; i < getbytes; i++)*buffer++ = *sp++;
+ }
+ buffer -= getbytes; /* reset to beginning of buffer */
+
+
+ if(noutlets == streamchannels)
+ { /* normal output */
+ for(j = 0; j < vecsize; j++)
+ {
+ for(i = 0; i < noutlets; i++)
+ {
+ x->x_outvec[i][j] = *buffer++;
+ }
+ }
+ }
+ else if((noutlets / 2) == streamchannels)
+ { /* mono to stereo conversion */
+ for(j = 0; j < vecsize; j++)
+ {
+ for(i = 0; i < noutlets; i++)
+ {
+ x->x_outvec[i][j] = *buffer;
+ }
+ buffer++;
+ }
+ }
+ else if((noutlets * 2) == streamchannels)
+ { /* stereo to mono conversion */
+ for(j = 0; j < vecsize; j++)
+ {
+ for(i = 0; i < streamchannels; i++)
+ {
+ x->x_outvec[i/2][j] += (float) (*buffer++ * 0.5);
+ }
+ }
+ }
+ else goto idle;
+
+
+ x->x_fifotail += wantbytes;
+ x->x_fifobytes -= wantbytes;
+ if (x->x_fifotail >= x->x_fifosize)
+ {
+ x->x_fifotail = 0;
+ }
+ /* signal the child thread */
+ if ((--x->x_sigcountdown) <= 0)
+ {
+ shoutamp_cond_signal(&x->x_requestcondition);
+ x->x_sigcountdown = x->x_sigperiod;
+ }
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ else
+ {
+ idle:
+ for (i = 0; i < noutlets; i++)
+ for (j = vecsize, fp = x->x_outvec[i]; j--; )
+ *fp++ = 0;
+ }
+
+ return (w+2);
+}
+
+
+static void shoutamp_disconnect(t_shoutamp *x)
+{
+ /* LATER rethink whether you need the mutex just to set a variable? */
+ pthread_mutex_lock(&x->x_mutex);
+ x->x_disconnect = 1;
+ x->x_state = STATE_IDLE;
+ x->x_requestcode = REQUEST_CLOSE;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+
+ /* connect method. Called as:
+ connect <hostname or IP> <mountpoint> <portnumber>
+ */
+
+static void shoutamp_connect(t_shoutamp *x, t_symbol *s, int argc, t_atom *argv)
+{
+ t_symbol *hostsym = atom_getsymbolarg(0, argc, argv);
+ t_symbol *mountsym = atom_getsymbolarg(1, argc, argv);
+ t_float portno = atom_getfloatarg(2, argc, argv);
+ if (!*hostsym->s_name) /* check for hostname */
+ return;
+ if (!portno) /* check wether the portnumber is specified */
+ portno = 8000; /* ...assume port 8000 as standard */
+ pthread_mutex_lock(&x->x_mutex);
+ if(x->x_fd == -1)
+ {
+ x->x_hostname = hostsym->s_name;
+ x->x_mountpoint = mountsym->s_name;
+ x->x_port = portno;
+ x->x_requestcode = REQUEST_CONNECT;
+ /* empty buffer */
+ x->x_fifotail = 0;
+ x->x_fifohead = 0;
+ x->x_fifobytes = 0;
+ x->x_mp3inpos = 0; /* also forget all mp3 data */
+ x->x_streamchannels = 2;
+ x->x_eof = 0;
+ x->x_connecterror = 0;
+ x->x_state = STATE_STARTUP;
+ x->x_disconnect = 0;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ }
+ else post("shoutamp~: already connected");
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+ /* connect using url like "http://localhost:8000/mountpoint" */
+static void shoutamp_connect_url(t_shoutamp *x, t_symbol *url)
+{
+ char *hname, *port;
+ char *h, *p;
+ char *hostptr;
+ char *r_hostptr;
+ char *pathptr;
+ char *portptr;
+ char *p0;
+ char *defaultportstr = "8000";
+ t_int stringlength;
+ t_int portno;
+
+ /* strip http:// or ftp:// */
+ p = url->s_name;
+ if (strncmp(p, "http://", 7) == 0)
+ p += 7;
+
+ if (strncmp(p, "ftp://", 6) == 0)
+ p += 6;
+
+ hostptr = p;
+ while (*p && *p != '/') /* look for end of hostname:port */
+ p++;
+ p++; /* also skip '/' */
+ pathptr = p;
+
+ r_hostptr = --p;
+ while (*p && hostptr < p && *p != ':' && *p != ']') /* split at ':' */
+ p--;
+
+ if (!*p || p < hostptr || *p != ':') {
+ portptr = NULL;
+ }
+ else{
+ portptr = p + 1;
+ r_hostptr = p - 1;
+ }
+ if (*hostptr == '[' && *r_hostptr == ']') {
+ hostptr++;
+ r_hostptr--;
+ }
+
+ stringlength = r_hostptr - hostptr + 1;
+ h = getbytes(stringlength + 1);
+ if (h == NULL) {
+ hname = NULL;
+ port = NULL;
+ pathptr = NULL;
+ }
+ strncpy(h, hostptr, stringlength);
+ *(h+stringlength) = '\0';
+ hname = h; /* the hostname */
+
+ if (portptr) {
+ stringlength = (pathptr - portptr);
+ if(!stringlength) portptr = NULL;
+ }
+ if (portptr == NULL) {
+ portptr = defaultportstr;
+ stringlength = strlen(defaultportstr);
+ }
+ p0 = getbytes(stringlength + 1);
+ if (p0 == NULL) {
+ freebytes(h, stringlength + 1);
+ hname = NULL;
+ port = NULL;
+ pathptr = NULL;
+ }
+ strncpy(p0, portptr, stringlength);
+ *(p0 + stringlength) = '\0';
+
+ for (p = p0; *p && isdigit((unsigned char) *p); p++) ;
+
+ *p = '\0';
+ port = (unsigned char *) p0;
+ /* convert port from string to int */
+ portno = (int)strtol(port, NULL, 10);
+ freebytes(p0, stringlength + 1);
+ /* set values and signal child to connect */
+ pthread_mutex_lock(&x->x_mutex);
+ if(x->x_fd == -1)
+ {
+ x->x_hostname = hname;
+ x->x_mountpoint = pathptr;
+ x->x_port = portno;
+ x->x_requestcode = REQUEST_CONNECT;
+ x->x_fifotail = 0;
+ x->x_fifohead = 0;
+ x->x_fifobytes = 0;
+ x->x_streamchannels = 2;
+ x->x_eof = 0;
+ x->x_connecterror = 0;
+ x->x_state = STATE_STARTUP;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ }
+ else post("shoutamp~: already connected");
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+static void shoutamp_float(t_shoutamp *x, t_floatarg f)
+{
+ if (f != 0)
+ {
+ pthread_mutex_lock(&x->x_mutex);
+ if(x->x_fd == -1)
+ {
+ x->x_requestcode = REQUEST_CONNECT;
+
+ x->x_fifotail = 0;
+ x->x_fifohead = 0;
+ x->x_fifobytes = 0;
+ x->x_streamchannels = 2;
+ x->x_eof = 0;
+ x->x_connecterror = 0;
+ x->x_state = STATE_STARTUP;
+ shoutamp_cond_signal(&x->x_requestcondition);
+ }
+ else post("shoutamp~: already connected");
+ pthread_mutex_unlock(&x->x_mutex);
+ }
+ else shoutamp_disconnect(x);
+}
+
+static void shoutamp_dsp(t_shoutamp *x, t_signal **sp)
+{
+ int i, noutlets = x->x_noutlets;
+ pthread_mutex_lock(&x->x_mutex);
+ x->x_vecsize = sp[0]->s_n;
+
+ x->x_sigperiod = (x->x_fifosize / (x->x_siginterval * x->x_streamchannels * x->x_vecsize));
+ for (i = 0; i < noutlets; i++)
+ x->x_outvec[i] = sp[i]->s_vec;
+ pthread_mutex_unlock(&x->x_mutex);
+ dsp_add(shoutamp_perform, 1, x);
+}
+
+static void shoutamp_print(t_shoutamp *x)
+{
+ pthread_mutex_lock(&x->x_mutex);
+ if(x->x_fd >= 0)
+ {
+ post("shoutamp~: connected to http://%s:%d/%s", x->x_hostname, x->x_port, x->x_mountpoint);
+ post("shoutamp~: bitstream is %d channels @ %ld Hz with %ldkbps nominal bitrate",
+ x->x_streamchannels, x->x_streamrate, x->x_bitrateindex / 1000);
+ } else post("shoutamp~: not connected");
+ if(x->x_recover == 0)
+ post("shoutamp~: recover mode set to \"disconnect\" (0)");
+ else if(x->x_recover == 1)
+ post("shoutamp~: recover mode set to \"reconnect\" (1)");
+ else if(x->x_recover == -1)
+ post("shoutamp~: recover mode set to \"resume\" (-1)");
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+ /* set behavior for buffer underruns */
+static void shoutamp_recover(t_shoutamp *x, t_floatarg f)
+{
+ pthread_mutex_lock(&x->x_mutex);
+ if(f <= -1)
+ { /* mute audio and try to fill buffer again: the default */
+ post("shoutamp~: set recover mode to \"resume\" (-1)");
+ f = -1;
+ }
+ else if(f >= 1)
+ { /* reconnect to server */
+ post("shoutamp~: set recover mode to \"reconnect\" (1)");
+ f = 1;
+ }
+ else
+ { /* disconnect from server */
+ post("shoutamp~: set recover mode to \"disconnect\" (0)");
+ f = 0;
+ }
+ x->x_recover = f;
+ pthread_mutex_unlock(&x->x_mutex);
+}
+
+static void shoutamp_free(t_shoutamp *x)
+{
+ /* request QUIT and wait for acknowledge */
+ void *threadrtn;
+ pthread_mutex_lock(&x->x_mutex);
+ x->x_requestcode = REQUEST_QUIT;
+ x->x_disconnect = 1;
+ post("stopping shoutamp thread...");
+ shoutamp_cond_signal(&x->x_requestcondition);
+ while (x->x_requestcode != REQUEST_NOTHING)
+ {
+ post("signalling...");
+ shoutamp_cond_signal(&x->x_requestcondition);
+ shoutamp_cond_wait(&x->x_answercondition, &x->x_mutex);
+ }
+ pthread_mutex_unlock(&x->x_mutex);
+ if (pthread_join(x->x_childthread, &threadrtn))
+ error("shoutamp_free: join failed");
+ post("... done.");
+
+ pthread_cond_destroy(&x->x_requestcondition);
+ pthread_cond_destroy(&x->x_answercondition);
+ pthread_mutex_destroy(&x->x_mutex);
+ freebytes(x->x_buf, x->x_bufsize*sizeof(t_float));
+ freebytes(x->x_rsbuf, sys_getblksize()*sizeof(t_float)*2);
+ clock_free(x->x_clock);
+}
+
+void shoutamp_tilde_setup(void)
+{
+ shoutamp_class = class_new(gensym("shoutamp~"), (t_newmethod)shoutamp_new,
+ (t_method)shoutamp_free, sizeof(t_shoutamp), 0, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, 0);
+ class_addfloat(shoutamp_class, (t_method)shoutamp_float);
+ class_addmethod(shoutamp_class, (t_method)shoutamp_disconnect, gensym("disconnect"), 0);
+ class_addmethod(shoutamp_class, (t_method)shoutamp_dsp, gensym("dsp"), 0);
+ class_addmethod(shoutamp_class, (t_method)shoutamp_connect, gensym("connect"), A_GIMME, 0);
+ class_addmethod(shoutamp_class, (t_method)shoutamp_connect_url, gensym("connecturl"), A_SYMBOL, 0);
+ class_addmethod(shoutamp_class, (t_method)shoutamp_recover, gensym("recover"), A_FLOAT, 0);
+ class_addmethod(shoutamp_class, (t_method)shoutamp_print, gensym("print"), 0);
+ class_sethelpsymbol(shoutamp_class, gensym("help-shoutamp~.pd"));
+} \ No newline at end of file