From badcb8e53fece5b0d573c6118c4ffdb8289e9e16 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 2 Apr 2003 15:46:30 +0000 Subject: 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 --- shoutamp~/shoutamp~.c | 1780 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1780 insertions(+) create mode 100644 shoutamp~/shoutamp~.c (limited to 'shoutamp~') 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 */ +/* Graphical buffer status display by Yves Degoyon . */ +/* 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 +#include +#include +#include +#include +#include +#include +#ifdef UNIX +#include +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#else +#include /* for 'write' in pute-function only */ +#include +#include +#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 /* debugging version... */ +#include +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 + */ + +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 -- cgit v1.2.1