/* ------------------------ shoutcast~ ---------------------------------------- */ /* */ /* Tilde object to send mp3-stream to shoutcast/icecast server. */ /* Written by Olaf Matthes . */ /* Original version ported to Linux 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" #include "lame/lame.h" /* lame encoder stuff */ #include #include #include #include #include #include #ifdef UNIX #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 #ifdef UNIX #define sys_closesocket close #endif #ifdef NT #define sys_closesocket closesocket #endif #ifdef __linux__ // 'real' linux only, not for OS X ! #define SEND_OPT MSG_DONTWAIT|MSG_NOSIGNAL #else #define SEND_OPT 0 #endif /************************* shoutcast~ object ******************************/ /* Each instance of shoutcast~ 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_REINIT 6 #define STATE_IDLE 0 #define STATE_STARTUP 1 /* connecting and filling the buffer */ #define STATE_STREAM 2 /* streaming and audio output */ #define DEFBUFPERCHAN 262144 /* audio output buffer by default: 256k */ #define ENCODER_CHANNELS 2 /* we allways use 2 channels internally */ #define MINBUFSIZE 65536 /* minimum size of ring buffer */ #define MAXBUFSIZE 16777216 /* arbitrary; just don't want to hang malloc */ #define STRBUF_SIZE 1024 /* char received from server on startup */ #define MAXSTREAMCHANS 2 /* maximum number of channels: restricted to 2 by Ogg specs */ #define MAXDATARATE 320 /* maximum mp3 data rate is 320kbit/s */ #define MY_MP3_MALLOC_IN_SIZE 65536 /* max size taken from lame readme */ #define MY_MP3_MALLOC_OUT_SIZE 1.25*MY_MP3_MALLOC_IN_SIZE+7200 /* max size of encoded output */ #define UPDATE_INTERVAL 250 /* time in milliseconds between updates of output values */ static char *shoutcast_version = "shoutcast~: mp3 streamer version 0.3h, written by Olaf Matthes"; static t_class *shoutcast_class; typedef struct _shoutcast { t_object x_obj; t_float *x_f; t_clock *x_clock_connect; t_clock *x_clock_pages; t_outlet *x_connection; /* outlet for connection state */ t_outlet *x_outpages; /* outlet for no. of ogg pages */ t_float *x_buf; /* audio data buffer */ t_int x_bufsize; /* buffer size in bytes */ t_int x_ninlets; /* 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 */ /* 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 */ /* 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_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) */ /* LAME stuff */ t_int x_lame; /* info about encoder status */ lame_global_flags *x_lgfp; t_int x_lamechunk; /* chunk size for LAME encoder */ short *x_mp3inbuf; /* data to be sent to LAME */ char *x_mp3outbuf; /* data returned by LAME -> our mp3 stream */ t_int x_mp3mode; /* mode (mono, joint stereo, stereo, dual mono) */ t_int x_mp3quality; /* quality of encoding */ t_int x_vbr; /* quality of VBR encoding if not -1 */ t_int x_eos; /* end of stream */ t_float x_streamedbytes; /* number of bytes that have been output to server */ t_float x_oldstreamedbytes; /* ringbuffer stuff */ t_float *x_buffer; /* data to be buffered (ringbuffer)*/ t_int x_bytesbuffered; /* number of unprocessed bytes in buffer */ /* mp3 stream format stuff */ t_int x_samplerate; /* samplerate of stream (default = getsr() ) */ t_int x_bitrate; /* bitrate of mp3 stream (CBR only) */ t_int x_channels; /* number of channels sent to the encoder */ /* this is allways 2 because of lame's software design */ /* IceCast server stuff */ char* x_passwd; /* password for server */ char* x_bcname; /* name of broadcast */ char* x_bcurl; /* url of broadcast */ char* x_bcgenre; /* genre of broadcast */ char* x_hostname; /* name or IP of host to connect to */ char* x_mountpoint; /* mountpoint for IceCast server */ t_float x_port; /* port number on which the connection is made */ t_int x_bcpublic; /* do(n't) publish broadcast on www.shoutcast.com */ t_int x_icecast; /* tells if we use a IceCast server (1) or SHOUTcast (0) */ t_int x_connectstate; /* indicates to state of socket connection */ t_int x_outvalue; /* value that has last been output via outlet */ t_int x_fd; /* the socket number */ /* tread stuff */ pthread_mutex_t x_mutex; pthread_cond_t x_requestcondition; pthread_cond_t x_answercondition; pthread_t x_childthread; } t_shoutcast; /* check server for writeability */ static int shoutcast_checkserver(t_int sock) { fd_set fdset; struct timeval ztout; fd_set writeset; fd_set exceptset; FD_ZERO(&writeset); FD_ZERO(&exceptset); FD_SET(sock, &writeset ); FD_SET(sock, &exceptset ); if(select(sock+1, NULL, &writeset, &exceptset, &ztout) > 0) { if(!FD_ISSET(sock, &writeset)) { post("shoutcast~: can not write data to the server, quitting"); return -1; } if(FD_ISSET(sock, &exceptset)) { post("shoutcast~: socket returned an error, quitting"); return -1; } } return 0; } /* lame decoder setup */ static int shoutcast_lame_init(t_shoutcast *x) { int ret; char *title = NULL; time_t now; int title_length; x->x_lgfp = lame_init(); /* set default parameters for now */ post( "shoutcast~: using lame version %s", get_lame_version() ); /* setting lame parameters */ if(x->x_mp3mode!=3) { lame_set_num_channels( x->x_lgfp, 2); } else { lame_set_num_channels( x->x_lgfp, 1); } /* maybe we're mono */ lame_set_in_samplerate( x->x_lgfp, sys_getsr() ); lame_set_out_samplerate( x->x_lgfp, x->x_samplerate ); if(x->x_vbr == -1) /* constand bitrate encoding (default) */ lame_set_brate( x->x_lgfp, x->x_bitrate ); else /* VBR encoding */ { lame_set_VBR( x->x_lgfp, vbr_rh ); lame_set_VBR_q( x->x_lgfp, x->x_vbr); } lame_set_mode( x->x_lgfp, x->x_mp3mode ); lame_set_quality( x->x_lgfp, x->x_mp3quality ); lame_set_emphasis( x->x_lgfp, 0 ); lame_set_original( x->x_lgfp, 1 ); lame_set_copyright( x->x_lgfp, 0 ); /* viva free music societies !!! */ lame_set_disable_reservoir( x->x_lgfp, 1 ); lame_set_padding_type( x->x_lgfp, PAD_NO ); ret = lame_init_params( x->x_lgfp ); if( ret<0 ) { error("shoutcast~: lame params initialization returned %d", ret); } else { x->x_lame = 1; /* magic formula copied from windows dll for MPEG-I */ if(x->x_mp3mode!=3) { x->x_lamechunk = 1152*2; } else { x->x_lamechunk = 1152; } post( "shoutcast~: lame initialization done"); } lame_init_bitstream(x->x_lgfp ); /* setting tag information */ id3tag_init(x->x_lgfp); id3tag_v1_only(x->x_lgfp); id3tag_space_v1(x->x_lgfp); id3tag_set_artist(x->x_lgfp, "Pd Session"); now=time(NULL); title_length = 256; if ( title != NULL ) { freebytes(title, title_length ); } title=(char*)getbytes(title_length); sprintf(title, "Started at %s", ctime(&now)); id3tag_set_title(x->x_lgfp, title); x->x_streamedbytes = x->x_oldstreamedbytes = 0; return (0); } /* deinit the lame decoder */ static void shoutcast_lame_deinit(t_shoutcast *x) { int err, ret; if(x->x_lame >= 0) { ret = lame_encode_flush( x->x_lgfp, x->x_mp3outbuf, MY_MP3_MALLOC_OUT_SIZE ); /* flush lame buffers */ err = send(x->x_fd, x->x_mp3outbuf, ret, SEND_OPT); if(err < 0) { error("shoutcast~: could not send flushed data to server (%d)", err); } lame_close( x->x_lgfp ); /* close lame */ } x->x_lame = 0; } /* encode lame and stream new data */ static int shoutcast_encode(t_shoutcast *x, float *buf, int channels, int fifosize, int fd) { unsigned short i, ch; int err = 0; int n; int mp3size; /* number of mp3 bytes returned from encoder */ /* read from buffer */ for(n = 0; n < x->x_lamechunk; n++) /* fill encode buffer */ { x->x_mp3inbuf[n] = (short) (32767.0 * (*buf++)); /* convert to PCM */ } /* encode mp3 data */ #ifndef UNIX /* the clear WinNT solution: */ mp3size = lame_encode_buffer_interleaved(x->x_lgfp, x->x_mp3inbuf, x->x_lamechunk / ENCODER_CHANNELS, x->x_mp3outbuf, MY_MP3_MALLOC_OUT_SIZE); #else /* is this really different under Linux ???? */ mp3size = lame_encode_buffer_interleaved(x->x_lgfp, x->x_mp3inbuf, x->x_lamechunk / lame_get_num_channels(x->x_lgfp), x->x_mp3outbuf, MY_MP3_MALLOC_OUT_SIZE); #endif // post( "shoutcast~: encoding returned %d frames", mp3size ); /* check result */ if(mp3size < 0) { lame_close( x->x_lgfp ); error("shoutcast~: lame_encode_buffer_interleaved failed (%d)",mp3size); x->x_lame = -1; return(-1); } /* stream to server */ err = send(fd, x->x_mp3outbuf, mp3size, SEND_OPT); if(err < 0) { error("shoutcast~: could not send encoded data to server (%d)", err); lame_close( x->x_lgfp ); x->x_lame = -1; return(-1); } if((err > 0)&&(err != mp3size))error("shoutcast~: %d bytes skipped", mp3size - err); return (mp3size); /* return number of bytes we encoded */ } /* connect to icecast2 server */ static int shoutcast_child_connect(char *hostname, char *mountpoint, t_int portno, char *passwd, char *bcname, char *bcurl, char *bcgenre, t_int bcpublic, t_int bitrate, t_int icecast) { struct sockaddr_in server; struct hostent *hp; /* variables used for communication with server */ const char * buf = 0; char resp[STRBUF_SIZE]; unsigned int len; fd_set fdset; struct timeval tv; int sockfd; int ret; if(icecast == 0)portno++; /* use SHOUTcast, portno is one higher */ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd < 0) { error("shoutcast~: 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("shoutcast~: 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); /* try to connect. */ post("shoutcast~: connecting to port %d", portno); if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) { error("shoutcast~: 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("shoutcast~: can not read from socket"); sys_closesocket(sockfd); return(-1); } /* ret = select(sockfd + 1, NULL, &fdset, NULL, &tv); if(ret < 0) { error("shoutcast~: can not write to socket"); sys_closesocket(sockfd); return; } */ if(icecast == 0) /* SHOUTCAST */ { /* now try to log in at SHOUTcast server */ post("shoutcast~: logging in to SHOUTcast server..."); /* first line is the passwd */ buf = passwd; send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\n"; send(sockfd, buf, strlen(buf), SEND_OPT); /* header for SHOUTcast server */ buf = "icy-name:"; /* name of broadcast */ send(sockfd, buf, strlen(buf), SEND_OPT); buf = bcname; send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\nicy-url:"; /* URL of broadcast */ send(sockfd, buf, strlen(buf), SEND_OPT); buf = bcurl; send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\nicy-genre:"; /* genre of broadcast */ send(sockfd, buf, strlen(buf), SEND_OPT); buf = bcgenre; send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\nicy-br:"; send(sockfd, buf, strlen(buf), SEND_OPT); if(sprintf(resp, "%d", bitrate) == -1) /* convert int to a string */ { error("shoutcast~: wrong bitrate"); } send(sockfd, resp, strlen(resp), SEND_OPT); buf = "\nicy-pub:"; send(sockfd, buf, strlen(buf), SEND_OPT); if(bcpublic==0) /* set the public flag for broadcast */ { buf = "0"; } else { buf ="1"; } send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\n\n"; send(sockfd, buf, strlen(buf), SEND_OPT); } else /* IceCast */ { /* now try to log in at IceCast server */ post("shoutcast~: logging in to IceCast server..."); /* send the request, a string like: "SOURCE /\n" */ buf = "SOURCE "; send(sockfd, buf, strlen(buf), SEND_OPT); buf = passwd; send(sockfd, buf, strlen(buf), SEND_OPT); buf = " /"; send(sockfd, buf, strlen(buf), SEND_OPT); buf = mountpoint; send(sockfd, buf, strlen(buf), SEND_OPT); /* send the x-audiocast headers */ buf = "\nx-audiocast-bitrate: "; send(sockfd, buf, strlen(buf), SEND_OPT); if(sprintf(resp, "%d", bitrate) == -1) /* convert int to a string */ { error("shoutcast~: wrong bitrate"); } send(sockfd, resp, strlen(resp), SEND_OPT); buf = "\nx-audiocast-public: "; send(sockfd, buf, strlen(buf), SEND_OPT); if(bcpublic==0) /* set the public flag for broadcast */ { buf = "0"; } else { buf ="1"; } send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\nx-audiocast-name: "; send(sockfd, buf, strlen(buf), SEND_OPT); buf = bcname; send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\nx-audiocast-url: "; send(sockfd, buf, strlen(buf), SEND_OPT); buf = bcurl; send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\nx-audiocast-genre: "; send(sockfd, buf, strlen(buf), SEND_OPT); buf = bcgenre; send(sockfd, buf, strlen(buf), SEND_OPT); buf = "\n\n"; send(sockfd, buf, strlen(buf), SEND_OPT); /* end login for IceCast */ } /* read the anticipated response: "OK" */ len = recv(sockfd, resp, STRBUF_SIZE, 0); if ( len < 2 || resp[0] != 'O' || resp[1] != 'K' ) { post("shoutcast~: login failed!"); sys_closesocket(sockfd); return(-1); } /* check if we can write to server */ if(shoutcast_checkserver(sockfd)!= 0) { post("shoutcast~: error: server refused to receive data"); sys_closesocket(sockfd); return (-1); } post("shoutcast~: logged in to http://%s:%d/%s", hp->h_name, portno, mountpoint); return (sockfd); } static void shoutcast_child_disconnect(t_int fd) { sys_closesocket(fd); post("shoutcast~: connection closed"); } /************** the child thread which performs data I/O ***********/ #if 0 /* set this to 1 to get debugging output */ static void pute(char *s) /* debug routine */ { write(2, s, strlen(s)); } #else #define pute(x) #endif #if 1 #define shoutcast_cond_wait pthread_cond_wait #define shoutcast_cond_signal pthread_cond_signal #else #include /* debugging version... */ #include static void shoutcast_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 shoutcast_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 shoutcast_cond_wait(a,b) shoutcast_fakewait(b) #define shoutcast_cond_signal(a) #endif static void *shoutcast_child_main(void *zz) { t_shoutcast *x = zz; pute("1\n"); pthread_mutex_lock(&x->x_mutex); while (1) { int fd, fifotail; pute("0\n"); if (x->x_requestcode == REQUEST_NOTHING) { pute("wait 2\n"); shoutcast_cond_signal(&x->x_answercondition); shoutcast_cond_wait(&x->x_requestcondition, &x->x_mutex); pute("3\n"); } // connect to Icecast2 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 connecting to server. */ char *hostname = x->x_hostname; char *mountpoint = x->x_mountpoint; t_int portno = x->x_port; char *passwd = x->x_passwd; char *bcname = x->x_bcname; char *bcgenre = x->x_bcgenre; char *bcurl = x->x_bcurl; t_int bcpublic = x->x_bcpublic; t_int br_nom = x->x_bitrate; t_int servertype = x->x_icecast; /* alter the request code so that an ensuing "open" will get noticed. */ pute("4\n"); x->x_requestcode = REQUEST_BUSY; x->x_connecterror = 0; /* open the socket with the mutex unlocked in case we're not already connected */ if(x->x_fd < 0) { pthread_mutex_unlock(&x->x_mutex); fd = shoutcast_child_connect(hostname, mountpoint, portno, passwd, bcname, bcurl, bcgenre, bcpublic, br_nom, servertype); pthread_mutex_lock(&x->x_mutex); pute("5\n"); /* copy back into the instance structure. */ x->x_connectstate = 1; clock_delay(x->x_clock_connect, 0); x->x_fd = fd; if (fd < 0) { x->x_connecterror = fd; x->x_connectstate = 0; clock_delay(x->x_clock_connect, 0); pute("connect failed\n"); goto lost; } else { x->x_streamedbytes = 0; clock_delay(x->x_clock_pages, 0); /* initialise the encoder */ if(shoutcast_lame_init(x) == 0) { post("shoutcast~: lame encoder initialised"); x->x_state = STATE_STREAM; } else { post("shoutcast~: could not init encoder"); shoutcast_child_disconnect(fd); post("shoutcast~: connection closed due to initialisation error"); x->x_fd = -1; x->x_connectstate = 0; clock_delay(x->x_clock_connect, 0); pute("shoutcast~: initialisation failed\n"); goto lost; } } x->x_fifotail = fifotail = 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_channels * x->x_lamechunk)); /* 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_channels * x->x_vecsize)); } /* check if another request has been made; if so, field it */ if (x->x_requestcode != REQUEST_BUSY) goto lost; pute("6\n"); while (x->x_requestcode == REQUEST_BUSY) { int fifosize = x->x_fifosize, fifotail, channels; float *buf = x->x_buf; pute("77\n"); /* if the head is < the tail, we can immediately write from tail to end of fifo to disk; otherwise we hold off writing until there are at least x->x_lamechunk bytes in the buffer */ if (x->x_fifohead < x->x_fifotail || x->x_fifohead >= x->x_fifotail + x->x_lamechunk || (x->x_requestcode == REQUEST_CLOSE && x->x_fifohead != x->x_fifotail)) { /* encode audio and send to server */ pute("8\n"); fifotail = x->x_fifotail; channels = x->x_channels; fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); sysrtn = shoutcast_encode(x, buf + fifotail, channels, fifosize, fd); pthread_mutex_lock(&x->x_mutex); if (x->x_requestcode != REQUEST_BUSY && x->x_requestcode != REQUEST_CLOSE) break; if (sysrtn < 0) { post("shoutcast~: closing due to error..."); goto lost; } else { x->x_fifotail += x->x_lamechunk; x->x_streamedbytes += sysrtn; if (x->x_fifotail >= fifosize) x->x_fifotail = 0; } sprintf(boo, "after: head %d, tail %d, pages %d\n", x->x_fifohead, x->x_fifotail, sysrtn); pute(boo); } else /* just wait... */ { pute("wait 7a ...\n"); shoutcast_cond_signal(&x->x_answercondition); pute("signalled\n"); shoutcast_cond_wait(&x->x_requestcondition, &x->x_mutex); pute("7a done\n"); continue; } /* signal parent in case it's waiting for data */ shoutcast_cond_signal(&x->x_answercondition); } } /* reinit encoder (settings have changed) */ else if (x->x_requestcode == REQUEST_REINIT) { pthread_mutex_unlock(&x->x_mutex); shoutcast_lame_deinit(x); shoutcast_lame_init(x); pthread_mutex_lock(&x->x_mutex); post("shoutcast~: lame encoder reinitialised"); x->x_state = STATE_STREAM; if (x->x_requestcode == REQUEST_REINIT) x->x_requestcode = REQUEST_CONNECT; shoutcast_cond_signal(&x->x_answercondition); } /* close connection to server (disconnect) */ else if (x->x_requestcode == REQUEST_CLOSE) { lost: x->x_state = STATE_IDLE; if (x->x_fd >= 0) { fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); shoutcast_lame_deinit(x); shoutcast_child_disconnect(fd); pthread_mutex_lock(&x->x_mutex); x->x_fd = -1; } x->x_requestcode = REQUEST_NOTHING; x->x_connectstate = 0; clock_delay(x->x_clock_connect, 0); shoutcast_cond_signal(&x->x_answercondition); // shoutcast_cond_signal(&x->x_requestcondition); // break; } // quit everything else if (x->x_requestcode == REQUEST_QUIT) { x->x_state = STATE_IDLE; if (x->x_fd >= 0) { fd = x->x_fd; pthread_mutex_unlock(&x->x_mutex); shoutcast_lame_deinit(x); shoutcast_child_disconnect(fd); pthread_mutex_lock(&x->x_mutex); x->x_fd = -1; } x->x_connectstate = 0; clock_delay(x->x_clock_connect, 0); x->x_requestcode = REQUEST_NOTHING; shoutcast_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 shoutcast_tick_connect(t_shoutcast *x) { pthread_mutex_lock(&x->x_mutex); outlet_float(x->x_connection, x->x_connectstate); pthread_mutex_unlock(&x->x_mutex); } static void shoutcast_tick_pages(t_shoutcast *x) { /* output kB we sent to server */ t_float bytes; pthread_mutex_lock(&x->x_mutex); bytes = x->x_streamedbytes; pthread_mutex_unlock(&x->x_mutex); outlet_float(x->x_outpages, (t_int)(bytes / 1024)); x->x_oldstreamedbytes = bytes; clock_delay(x->x_clock_pages, UPDATE_INTERVAL); } static void *shoutcast_new(t_floatarg fnchannels, t_floatarg fbufsize) { t_shoutcast *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 <= 0) 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_shoutcast *)pd_new(shoutcast_class); for (i = 1; i < nchannels; i++) inlet_new (&x->x_obj, &x->x_obj.ob_pd, gensym ("signal"), gensym ("signal")); x->x_connection = outlet_new(&x->x_obj, gensym("float")); x->x_outpages = outlet_new(&x->x_obj, gensym("float")); x->x_ninlets = nchannels; x->x_clock_connect = clock_new(x, (t_method)shoutcast_tick_connect); x->x_clock_pages = clock_new(x, (t_method)shoutcast_tick_pages); x->x_mp3inbuf = getbytes(MY_MP3_MALLOC_IN_SIZE*sizeof(short)); /* buffer for encoder input */ x->x_mp3outbuf = getbytes(MY_MP3_MALLOC_OUT_SIZE*sizeof(char)); /* our mp3 stream */ pthread_mutex_init(&x->x_mutex, 0); pthread_cond_init(&x->x_requestcondition, 0); pthread_cond_init(&x->x_answercondition, 0); x->x_vecsize = 2; x->x_state = STATE_IDLE; x->x_buf = buf; x->x_bufsize = bufsize; x->x_siginterval = 32; /* signal 32 times per buffer */ x->x_fifosize = x->x_fifohead = x->x_fifotail = x->x_requestcode = 0; x->x_connectstate = 0; /* indicating state of connection */ x->x_outvalue = 0; /* value at output currently is 0 */ x->x_samplerate = sys_getsr(); x->x_fd = -1; x->x_vbr = -1; /* don't use the vbr setting by default */ x->x_passwd = "letmein"; x->x_samplerate = sys_getsr(); /* default to Pd's sampling rate */ x->x_mp3quality = 5; /* quality 5 - medium CPU usage */ x->x_channels = ENCODER_CHANNELS; x->x_lamechunk = 2304; /* the mp3 chunk size for stereo */ x->x_bitrate = 128; x->x_streamedbytes = x->x_oldstreamedbytes = 0; x->x_bcname = "mp3 stream emitted from Pure Data"; x->x_bcurl = "http://www.akustische-kunst.org/"; x->x_bcgenre = "experimental"; x->x_bcpublic = 1; x->x_mountpoint = "puredata.mp3"; x->x_hostname = "localhost"; x->x_port = 8000; post(shoutcast_version); post("shoutcast~: set buffer to %dk bytes", bufsize / 1024); post("shoutcast~: encoding %d channels @ %d Hz", x->x_channels, x->x_samplerate); if(x->x_ninlets > ENCODER_CHANNELS) { post("shoutcast~: a maximum of 2 channels is supported"); x->x_ninlets = 2; } clock_delay(x->x_clock_pages, 0); pthread_create(&x->x_childthread, 0, shoutcast_child_main, x); return (x); } static t_int *shoutcast_perform(t_int *w) { t_shoutcast *x = (t_shoutcast *)(w[1]); int vecsize = x->x_vecsize, ninlets = x->x_ninlets, channels = x->x_channels, i, j, k; float *sp = x->x_buf; pthread_mutex_lock(&x->x_mutex); if (x->x_state != STATE_IDLE) /* don't fill buffers in case everything's turned off */ { int wantbytes; /* get 'wantbytes' bytes from input */ wantbytes = channels * vecsize; /* we need vecsize bytes per channel */ /* check if there is enough space in buffer to write samples */ while((x->x_fifotail > x->x_fifohead && x->x_fifotail < x->x_fifohead + wantbytes + 1) && x->x_fd > 0) { pute("wait...\n"); shoutcast_cond_signal(&x->x_requestcondition); shoutcast_cond_wait(&x->x_answercondition, &x->x_mutex); pute("done\n"); } /* output audio */ sp += x->x_fifohead; if(ninlets >= channels) { for(j = 0; j < vecsize; j++) { for(i = 0; i < channels; i++) { *sp++ = x->x_outvec[i][j]; } } } else if(channels > ninlets) /* convert mono -> stereo */ { for(j = 0; j < vecsize; j++) { for(i = 0; i < ninlets; i++) { for(k = 0; k < channels; k++) *sp++ = x->x_outvec[i][j]; } } } x->x_fifohead += wantbytes; if (x->x_fifohead >= x->x_fifosize) x->x_fifohead = 0; /* signal the child thread */ if ((--x->x_sigcountdown) <= 0) { shoutcast_cond_signal(&x->x_requestcondition); x->x_sigcountdown = x->x_sigperiod; } } pthread_mutex_unlock(&x->x_mutex); return (w+2); } static void shoutcast_disconnect(t_shoutcast *x) { /* LATER rethink whether you need the mutex just to set a variable? */ pthread_mutex_lock(&x->x_mutex); if(x->x_fd >= 0) { x->x_state = STATE_IDLE; x->x_requestcode = REQUEST_CLOSE; shoutcast_cond_signal(&x->x_requestcondition); } else post("shoutcast~: not connected"); pthread_mutex_unlock(&x->x_mutex); } /* connect method. Called as: connect */ static void shoutcast_connect(t_shoutcast *x, t_symbol *s, int argc, t_atom *argv) { t_symbol *hostsym, *mountsym; t_float portno; if(argc > 0)hostsym = atom_getsymbolarg(0, argc, argv); if(argc > 1)mountsym = atom_getsymbolarg(1, argc, argv); if(argc > 2)portno = atom_getfloatarg(2, argc, argv); pthread_mutex_lock(&x->x_mutex); if(x->x_fd >= 0) { post("shoutcast~: already connected"); } else { x->x_requestcode = REQUEST_CONNECT; if(argc >= 1)x->x_hostname = hostsym->s_name; if(argc >= 2)x->x_mountpoint = mountsym->s_name; if(argc >= 3)x->x_port = portno; x->x_fifotail = 0; x->x_fifohead = 0; x->x_connecterror = 0; x->x_streamedbytes = x->x_oldstreamedbytes = 0; x->x_state = STATE_STARTUP; shoutcast_cond_signal(&x->x_requestcondition); } pthread_mutex_unlock(&x->x_mutex); } static void shoutcast_float(t_shoutcast *x, t_floatarg f) { if (f != 0) { pthread_mutex_lock(&x->x_mutex); if(x->x_fd >= 0) { post("shoutcast~: already connected"); } else { x->x_requestcode = REQUEST_CONNECT; x->x_fifotail = 0; x->x_fifohead = 0; x->x_connecterror = 0; x->x_state = STATE_STARTUP; shoutcast_cond_signal(&x->x_requestcondition); } pthread_mutex_unlock(&x->x_mutex); } else shoutcast_disconnect(x); } static void shoutcast_dsp(t_shoutcast *x, t_signal **sp) { int i, ninlets = x->x_ninlets; pthread_mutex_lock(&x->x_mutex); x->x_vecsize = sp[0]->s_n; x->x_sigperiod = (x->x_fifosize / (x->x_siginterval * ninlets * x->x_vecsize)); for (i = 0; i < ninlets; i++) x->x_outvec[i] = sp[i]->s_vec; pthread_mutex_unlock(&x->x_mutex); dsp_add(shoutcast_perform, 1, x); } /* set password for shoutcast server */ static void shoutcast_password(t_shoutcast *x, t_symbol *password) { pthread_mutex_lock(&x->x_mutex); x->x_passwd = password->s_name; pthread_mutex_unlock(&x->x_mutex); } /* set broadcast name */ static void shoutcast_bcname(t_shoutcast *x, t_symbol *s, int ac, t_atom* av) { t_binbuf *b = binbuf_new(); char* text; int length; binbuf_add(b, ac, av); binbuf_gettext(b, &text, &length); // text[length] = '\0'; pthread_mutex_lock(&x->x_mutex); x->x_bcname = text; post("shoutcast~: set broadcast name to:\n %s", x->x_bcname); if(x->x_state == STATE_STREAM) post("shoutcast~: reconnect to make changes take effect!"); pthread_mutex_unlock(&x->x_mutex); binbuf_free(b); } /* set broadcast genre */ static void shoutcast_bcgenre(t_shoutcast *x, t_symbol *s, int ac, t_atom* av) { t_binbuf *b = binbuf_new(); char* text; int length; binbuf_add(b, ac, av); binbuf_gettext(b, &text, &length); // text[length] = '\0'; pthread_mutex_lock(&x->x_mutex); x->x_bcgenre = text; post("shoutcast~: set broadcast genre to:\n %s", x->x_bcgenre); if(x->x_state == STATE_STREAM) post("shoutcast~: reconnect to make changes take effect!"); pthread_mutex_unlock(&x->x_mutex); binbuf_free(b); } /* set broadcast URL */ static void shoutcast_bcurl(t_shoutcast *x, t_symbol *s, int ac, t_atom* av) { t_binbuf *b = binbuf_new(); char* text; int length; binbuf_add(b, ac, av); binbuf_gettext(b, &text, &length); // text[length] = '\0'; pthread_mutex_lock(&x->x_mutex); x->x_bcurl = text; post("shoutcast~: set broadcast URL to:\n %s", x->x_bcurl); if(x->x_state == STATE_STREAM) post("shoutcast~: reconnect to make changes take effect!"); pthread_mutex_unlock(&x->x_mutex); binbuf_free(b); } /* set broadcast public */ static void shoutcast_bcpublic(t_shoutcast *x, t_floatarg fp) { pthread_mutex_lock(&x->x_mutex); x->x_bcpublic = (t_int)fp; if(x->x_bcpublic == 0) post("shoutcast~: set broadcast to not public"); else post("shoutcast~: set broadcast to public"); if(x->x_state == STATE_STREAM) post("shoutcast~: reconnect to make changes take effect!"); pthread_mutex_unlock(&x->x_mutex); } static void shoutcast_icecast(t_shoutcast *x) { x->x_icecast = 1; post("shoutcast~: set server type to IceCast"); } /* we use SHOUTcast server (default) */ static void shoutcast_shoutcast(t_shoutcast *x) { x->x_icecast = 0; post("shoutcast~: set server type to SHOUTcast"); } /* settings for mp3 encoding */ static void shoutcast_mpeg(t_shoutcast *x, t_floatarg fsamplerate, t_floatarg fbitrate, t_floatarg fmode, t_floatarg fquality) { char *mode[4] = { "stereo", "joint stereo", "dual channel", "mono"}; pthread_mutex_lock(&x->x_mutex); x->x_samplerate = fsamplerate; if(fbitrate > MAXDATARATE) { fbitrate = MAXDATARATE; } x->x_bitrate = (t_int)fbitrate; x->x_mp3mode = (t_int)fmode; x->x_vbr = -1; x->x_channels = ENCODER_CHANNELS; x->x_mp3quality = (t_int)fquality; post("shoutcast~: mp3 stream: CBR, %dHz, %dkbit/s, %s (mode %d), quality %d", x->x_samplerate, x->x_bitrate, mode[x->x_mp3mode], x->x_mp3mode, x->x_mp3quality); if(x->x_fd>=0)post("shoutcast~: reconnect to make changes take effect! "); pthread_mutex_unlock(&x->x_mutex); } /* settings for vbr mp3 encoding */ static void shoutcast_vbr(t_shoutcast *x, t_floatarg fsamplerate, t_floatarg fmode, t_floatarg fquality) { char *mode[4] = { "stereo", "joint stereo", "dual channel", "mono"}; pthread_mutex_lock(&x->x_mutex); x->x_samplerate = fsamplerate; x->x_bitrate = 0; x->x_vbr = (t_int)fquality; x->x_mp3mode = (t_int)fmode; x->x_channels = 2; post("shoutcast~: mp3 stream: VBR, %dHz, %s (mode %d), quality %d", x->x_samplerate, mode[x->x_mp3mode], x->x_mp3mode, x->x_vbr); if(x->x_fd>=0)post("shoutcast~: reconnect to make changes take effect! "); pthread_mutex_unlock(&x->x_mutex); } /* print settings to pd's console window */ static void shoutcast_print(t_shoutcast *x) { const char *mode[4] = { "stereo", "joint stereo", "dual channel", "mono" }; pthread_mutex_lock(&x->x_mutex); post(shoutcast_version); post(" LAME mp3 settings:\n" " output sample rate: %d Hz", x->x_samplerate); post(" mode: %s", mode[x->x_mp3mode]); if(x->x_vbr == -1) { post(" bitrate: %d kB/s", x->x_bitrate); post(" quality: %d", x->x_mp3quality); } else { post(" using variable bitrate encoding"); post(" quality: %d", x->x_vbr); } post(" mp3 chunk size: %d", x->x_lamechunk); if(x->x_samplerate!=sys_getsr()) { post(" resampling from %d to %d Hz!", (t_int)sys_getsr(), x->x_samplerate); } if(x->x_icecast == 0) { post(" server type: SHOUTcast"); if(x->x_fd > 0) post(" server state: connected"); else post(" server state: not connected"); post(" URL: http://%s:%d/", x->x_hostname, (t_int)x->x_port); } else { post(" server type: IceCast"); if(x->x_fd > 0) post(" server state: connected"); else post(" server state: not connected"); post(" stream URL: http://%s:%d/%s", x->x_hostname, (t_int)x->x_port, x->x_mountpoint); } pthread_mutex_unlock(&x->x_mutex); } /* print lame settings to pd's console window */ static void shoutcast_printlame(t_shoutcast *x) { pthread_mutex_lock(&x->x_mutex); if(x->x_state == STATE_STREAM) lame_print_config(x->x_lgfp); else post("shoutcast~: can't print, lame is not running"); pthread_mutex_unlock(&x->x_mutex); } static void shoutcast_free(t_shoutcast *x) { /* request QUIT and wait for acknowledge */ void *threadrtn; pthread_mutex_lock(&x->x_mutex); x->x_requestcode = REQUEST_QUIT; post("stopping shoutcast thread..."); shoutcast_cond_signal(&x->x_requestcondition); while (x->x_requestcode != REQUEST_NOTHING) { post("signalling..."); shoutcast_cond_signal(&x->x_requestcondition); shoutcast_cond_wait(&x->x_answercondition, &x->x_mutex); } pthread_mutex_unlock(&x->x_mutex); if (pthread_join(x->x_childthread, &threadrtn)) error("shoutcast_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_mp3inbuf, MY_MP3_MALLOC_IN_SIZE*sizeof(short)); freebytes(x->x_mp3outbuf, MY_MP3_MALLOC_OUT_SIZE); clock_free(x->x_clock_connect); clock_free(x->x_clock_pages); } void shoutcast_tilde_setup(void) { shoutcast_class = class_new(gensym("shoutcast~"), (t_newmethod)shoutcast_new, (t_method)shoutcast_free, sizeof(t_shoutcast), 0, A_DEFFLOAT, A_DEFFLOAT, 0); CLASS_MAINSIGNALIN(shoutcast_class, t_shoutcast, x_f ); class_addfloat(shoutcast_class, (t_method)shoutcast_float); class_addmethod(shoutcast_class, (t_method)shoutcast_disconnect, gensym("disconnect"), 0); class_addmethod(shoutcast_class, (t_method)shoutcast_dsp, gensym("dsp"), 0); class_addmethod(shoutcast_class, (t_method)shoutcast_connect, gensym("connect"), A_GIMME, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_print, gensym("print"), 0); class_addmethod(shoutcast_class, (t_method)shoutcast_printlame, gensym("printlame"), 0); class_addmethod(shoutcast_class, (t_method)shoutcast_icecast, gensym("icecast"), 0); class_addmethod(shoutcast_class, (t_method)shoutcast_shoutcast, gensym("shoutcast"), 0); class_addmethod(shoutcast_class, (t_method)shoutcast_password, gensym("passwd"), A_SYMBOL, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_mpeg, gensym("mpeg"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_mpeg, gensym("cbr"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_vbr, gensym("vbr"), A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_bcname, gensym("name"), A_GIMME, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_bcgenre, gensym("genre"), A_GIMME, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_bcurl, gensym("url"), A_GIMME, 0); class_addmethod(shoutcast_class, (t_method)shoutcast_bcpublic, gensym("public"), A_FLOAT, 0); class_sethelpsymbol(shoutcast_class, gensym("help-shoutcast~.pd")); }