From ced726412c09f7fd3ef2eab1ff59305a720c03ec Mon Sep 17 00:00:00 2001 From: jdl Date: Sun, 4 Jan 2004 12:16:08 +0000 Subject: recommitting olaf's sources from 0.25 svn path=/trunk/externals/pdogg/; revision=1249 --- oggcast~/oggcast~.c | 2879 ++++++++++++++++++++++++++------------------------- 1 file changed, 1444 insertions(+), 1435 deletions(-) (limited to 'oggcast~/oggcast~.c') diff --git a/oggcast~/oggcast~.c b/oggcast~/oggcast~.c index f27849c..c5dfc5f 100644 --- a/oggcast~/oggcast~.c +++ b/oggcast~/oggcast~.c @@ -1,1435 +1,1444 @@ -/* ------------------------- oggcast~ ----------------------------------------- */ -/* */ -/* Tilde object to send an Ogg Vorbis stream from to IceCast2 server. */ -/* Written by Olaf Matthes (olaf.matthes@gmx.de) */ -/* Get source at http://www.akustische-kunst.org/puredata/pdogg/ */ -/* */ -/* 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 Ogg Vorbis decoding library which can be found at */ -/* http://www.vorbis.com/ */ -/* */ -/* ---------------------------------------------------------------------------- */ - - /* Pd includes */ -#include "m_pd.h" -#include "g_canvas.h" - /* Vorbis includes */ -#include "codec.h" -#include "vorbisenc.h" - -#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 - -/************************* oggcast~ object ******************************/ - -/* Each instance of oggcast~ 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 aund audio output */ - -#define READ 4096 /* amount of data we pass on to encoder */ -#define DEFBUFPERCHAN 262144 /* audio output buffer by default: 256k */ -#define MINBUFSIZE 65536 -#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 UPDATE_INTERVAL 250 /* time in milliseconds between updates of output values */ - -static char *oggcast_version = "oggcast~: ogg/vorbis streaming client version 0.2i, written by Olaf Matthes"; - -static t_class *oggcast_class; - -typedef struct _oggcast -{ - 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) */ - - /* ogg/vorbis related stuff */ - ogg_stream_state x_os; /* take physical pages, weld into a logical stream of packets */ - ogg_page x_og; /* one Ogg bitstream page. Vorbis packets are inside */ - ogg_packet x_op; /* one raw packet of data for decode */ - vorbis_info x_vi; /* struct that stores all the static vorbis bitstream settings */ - vorbis_comment x_vc; /* struct that stores all the user comments */ - vorbis_dsp_state x_vd; /* central working state for the packet->PCM decoder */ - vorbis_block x_vb; /* local working space for packet->PCM decode */ - - t_int x_eos; /* end of stream */ - t_float x_pages; /* number of pages that have been output to server */ - t_float x_lastpages; - - /* ringbuffer stuff */ - t_float *x_buffer; /* data to be buffered (ringbuffer)*/ - t_int x_bytesbuffered; /* number of unprocessed bytes in buffer */ - - /* ogg/vorbis format stuff */ - t_int x_samplerate; /* samplerate of stream (default = getsr() ) */ - t_int x_skip; /* samples from input to skip (for resampling) */ - t_float x_quality; /* desired quality level from 0.0 to 1.0 (lo to hi) */ - t_int x_br_max; /* max. bitrate of ogg/vorbis stream */ - t_int x_br_nom; /* nom. bitrate of ogg/vorbis stream */ - t_int x_br_min; /* min. bitrate of ogg/vorbis stream */ - t_int x_channels; /* number of channels (1 or 2) */ - t_int x_vbr; - - /* 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_bcdescription; /* description */ - char* x_bcartist; /* artist */ - char* x_bclocation; - char* x_bccopyright; - char* x_bcperformer; - char* x_bccontact; - char* x_bcdate; /* system date when broadcast started */ - 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.oggcast.com */ - t_int x_servertype; /* type of server: 0 = JRoar or old Icecast2; 1 = new Icecast2 */ - - - - - 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 */ - t_resample x_resample; /* resampling unit */ - t_int x_recover; /* indicate how to behave on buffer underruns */ - - /* thread stuff */ - pthread_mutex_t x_mutex; - pthread_cond_t x_requestcondition; - pthread_cond_t x_answercondition; - pthread_t x_childthread; -} t_oggcast; - - -static char base64table[65] = { - 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', - 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', - 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', - 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/', -}; - - /* This isn't efficient, but it doesn't need to be */ -char *oggcast_util_base64_encode(char *data) -{ - int len = strlen(data); - char *out = (char *)getbytes(len*4/3 + 4); - char *result = out; - int chunk; - - while(len > 0) { - chunk = (len >3)?3:len; - *out++ = base64table[(*data & 0xFC)>>2]; - *out++ = base64table[((*data & 0x03)<<4) | ((*(data+1) & 0xF0) >> 4)]; - - switch(chunk) { - case 3: - *out++ = base64table[((*(data+1) & 0x0F)<<2) | ((*(data+2) & 0xC0)>>6)]; - *out++ = base64table[(*(data+2)) & 0x3F]; - break; - case 2: - *out++ = base64table[((*(data+1) & 0x0F)<<2)]; - *out++ = '='; - break; - case 1: - *out++ = '='; - *out++ = '='; - break; - } - data += chunk; - len -= chunk; - } - *out = 0; - - return result; -} - - /* check server for writeability */ -static int oggcast_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("oggcast~: can not write data to the server, quitting"); - return -1; - } - if(FD_ISSET(sock, &exceptset)) - { - post("oggcast~: socket returned an error, quitting"); - return -1; - } - } - return 0; -} - - /* stream ogg/vorbis to IceCast2 server */ -static int oggcast_stream(t_oggcast *x, t_int fd) -{ - int err = -1; /* error return code */ - int pages = 0; - - /* write out pages (if any) */ - while(!x->x_eos) - { - int result=ogg_stream_pageout(&(x->x_os),&(x->x_og)); - if(result==0)break; - err = send(fd, x->x_og.header, x->x_og.header_len, 0); - if(err < 0) - { - error("oggcast~: could not send ogg header to server (%d)", err); - x->x_eos = 1; /* indicate (artificial) end of stream */ - return err; - } - err = send(fd, x->x_og.body, x->x_og.body_len, 0); - if(err < 0) - { - error("oggcast~: could not send ogg body to server (%d)", err); - x->x_eos = 1; /* indicate (artificial) end of stream */ - return err; - } - pages++; /* count number of pages */ - /* there might be more than one pages we have to send */ - if(ogg_page_eos(&(x->x_og)))x->x_eos=1; - } - return (pages); -} - - /* ogg/vorbis decoder setup */ -static int oggcast_vorbis_init(t_oggcast *x) -{ - int err = -1; - - x->x_eos = 0; - x->x_skip = 1; /* assume no resampling */ - /* choose an encoding mode */ - vorbis_info_init(&(x->x_vi)); - - if(x->x_samplerate != sys_getsr()) /* downsampling for Oliver (http://radiostudio.org) */ - { - if(sys_getsr() / x->x_samplerate == 2.0) - { - post("oggcast~: downsampling from %.0f to %d Hz", sys_getsr(), x->x_samplerate); - x->x_skip = 2; - } - else if(sys_getsr() / x->x_samplerate == 4.0) - { - post("oggcast~: downsampling from %.0f to %d Hz", sys_getsr(), x->x_samplerate); - x->x_skip = 4; - } - else if(sys_getsr() / x->x_samplerate == 3.0) - { - post("oggcast~: downsampling from %.0f to %d Hz", sys_getsr(), x->x_samplerate); - x->x_skip = 3; - } else post("oggcast~: warning: resampling from %.0f to %d not supported", sys_getsr(), x->x_samplerate); - } - if(x->x_vbr == 1) - { /* quality based setting */ - if(vorbis_encode_init_vbr(&(x->x_vi), x->x_channels, x->x_samplerate, x->x_quality)) - { - post("oggcast~: ogg/vorbis mode initialisation failed: invalid parameters for quality"); - vorbis_info_clear(&(x->x_vi)); - return (-1); - } - } - else - { /* bitrate based setting */ - if(vorbis_encode_init(&(x->x_vi), x->x_channels, x->x_samplerate, x->x_br_max*1024, x->x_br_nom*1024, x->x_br_min*1024)) - { - post("oggcast~: ogg/vorbis mode initialisation failed: invalid parameters for quality"); - vorbis_info_clear(&(x->x_vi)); - return (-1); - } - } - - /* add a comment */ - vorbis_comment_init(&(x->x_vc)); - vorbis_comment_add_tag(&(x->x_vc),"TITLE", x->x_bcname); - vorbis_comment_add_tag(&(x->x_vc),"ARTIST", x->x_bcartist); - vorbis_comment_add_tag(&(x->x_vc),"GENRE",x->x_bcgenre); - vorbis_comment_add_tag(&(x->x_vc),"DESCRIPTION", x->x_bcdescription); - vorbis_comment_add_tag(&(x->x_vc),"LOCATION",x->x_bclocation); - vorbis_comment_add_tag(&(x->x_vc),"PERFORMER",x->x_bcperformer); - vorbis_comment_add_tag(&(x->x_vc),"COPYRIGHT",x->x_bccopyright); - vorbis_comment_add_tag(&(x->x_vc),"CONTACT",x->x_bccontact); - vorbis_comment_add_tag(&(x->x_vc),"DATE",x->x_bcdate); - vorbis_comment_add_tag(&(x->x_vc),"ENCODER","oggcast~ v0.2 for pure-data"); - - /* set up the analysis state and auxiliary encoding storage */ - vorbis_analysis_init(&(x->x_vd),&(x->x_vi)); - vorbis_block_init(&(x->x_vd),&(x->x_vb)); - - /* set up our packet->stream encoder */ - /* pick a random serial number; that way we can more likely build - chained streams just by concatenation */ - srand(time(NULL)); - ogg_stream_init(&(x->x_os),rand()); - - /* Vorbis streams begin with three headers; the initial header (with - most of the codec setup parameters) which is mandated by the Ogg - bitstream spec. The second header holds any comment fields. The - third header holds the bitstream codebook. We merely need to - make the headers, then pass them to libvorbis one at a time; - libvorbis handles the additional Ogg bitstream constraints */ - - { - ogg_packet header; - ogg_packet header_comm; - ogg_packet header_code; - - vorbis_analysis_headerout(&(x->x_vd),&(x->x_vc),&header,&header_comm,&header_code); - ogg_stream_packetin(&(x->x_os),&header); /* automatically placed in its own page */ - ogg_stream_packetin(&(x->x_os),&header_comm); - ogg_stream_packetin(&(x->x_os),&header_code); - - /* We don't have to write out here, but doing so makes streaming - * much easier, so we do, flushing ALL pages. This ensures the actual - * audio data will start on a new page - * - * IceCast2 server will take this as a first info about our stream - */ - while(!x->x_eos) - { - int result=ogg_stream_flush(&(x->x_os),&(x->x_og)); - if(result==0)break; - err = send(x->x_fd, x->x_og.header, x->x_og.header_len, 0); - if(err < 0) - { - error("oggcast~: could not send ogg header to server (%d)", err); - x->x_eos = 1; /* indicate end of stream */ - return (-1); - } - err = send(x->x_fd, x->x_og.body, x->x_og.body_len, 0); - if(err < 0) - { - error("oggcast~: could not send ogg body to server (%d)", err); - x->x_eos = 1; /* indicate end of stream */ - return (-1); - } - } - } - return (0); -} - - /* deinit the ogg/vorbis decoder */ -static void oggcast_vorbis_deinit(t_oggcast *x) -{ - vorbis_analysis_wrote(&(x->x_vd),0); - /* get rid of remaining data in encoder, if any */ - while(vorbis_analysis_blockout(&(x->x_vd),&(x->x_vb))==1) - { - vorbis_analysis(&(x->x_vb),NULL); - vorbis_bitrate_addblock(&(x->x_vb)); - - while(vorbis_bitrate_flushpacket(&(x->x_vd),&(x->x_op))) - { - ogg_stream_packetin(&(x->x_os),&(x->x_op)); - oggcast_stream(x, x->x_fd); - } - } - /* clean up and exit. vorbis_info_clear() must be called last */ - ogg_stream_clear(&(x->x_os)); - vorbis_block_clear(&(x->x_vb)); - vorbis_dsp_clear(&(x->x_vd)); - vorbis_comment_clear(&(x->x_vc)); - vorbis_info_clear(&(x->x_vi)); -} - - /* encode ogg/vorbis and stream new data */ -static int oggcast_encode(t_oggcast *x, float *buf, int channels, int fifosize, int fd) -{ - unsigned short i, ch; - int err = 0; - int n, pages = 0; - - /* expose the buffer to submit data */ - float **inbuffer=vorbis_analysis_buffer(&(x->x_vd),READ); - - /* read from buffer */ - for(n = 0; n < READ / channels; n++) /* fill encode buffer */ - { - for(ch = 0; ch < channels; ch++) - { - inbuffer[ch][n] = *buf++; - } - } - /* tell the library how much we actually submitted */ - vorbis_analysis_wrote(&(x->x_vd),n); - - /* vorbis does some data preanalysis, then divvies up blocks for - more involved (potentially parallel) processing. Get a single - block for encoding now */ - while(vorbis_analysis_blockout(&(x->x_vd),&(x->x_vb))==1) - { - /* analysis, assume we want to use bitrate management */ - vorbis_analysis(&(x->x_vb),NULL); - vorbis_bitrate_addblock(&(x->x_vb)); - - while(vorbis_bitrate_flushpacket(&(x->x_vd),&(x->x_op))) - { - /* weld the packet into the bitstream */ - ogg_stream_packetin(&(x->x_os),&(x->x_op)); - err = oggcast_stream(x, fd); /* stream packet to server */ - if(err >= 0) - { - pages += err; /* count pages */ - } - else return (err); - } - } - return (pages); -} - - /* connect to icecast2 server */ -static int oggcast_child_connect(char *hostname, char *mountpoint, t_int portno, - char *passwd, char *bcname, char *bcurl, - char *bcgenre, t_int bcpublic, t_int br_nom, t_int servertype) -{ - 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; - t_int sockfd; /* our internal handle for the socket */ - t_int ret; - - sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sockfd < 0) - { - error("oggcast~: 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("oggcast~: 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("oggcast~: connecting to port %d", portno); - if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) - { - error("oggcast~: 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("oggcast~: can not read from socket"); - sys_closesocket(sockfd); - return (-1); - } - - post("oggcast~: logging in to IceCast2 server..."); - /* now try to log in at IceCast2 server using ICE/1.0 scheme */ - if(servertype == 0) - { - /* send the request, a string like: "SOURCE / ICE/1.0\n" */ - buf = "SOURCE "; - send(sockfd, buf, strlen(buf), 0); - buf = "/"; - send(sockfd, buf, strlen(buf), 0); - buf = mountpoint; - send(sockfd, buf, strlen(buf), 0); - buf = " ICE/1.0"; - send(sockfd, buf, strlen(buf), 0); - /* send the ice headers */ - /* password */ - buf = "\nice-password: "; - send(sockfd, buf, strlen(buf), 0); - buf = passwd; - send(sockfd, buf, strlen(buf), 0); - /* name */ - buf = "\r\nice-name: "; - send(sockfd, buf, strlen(buf), 0); - buf = bcname; - send(sockfd, buf, strlen(buf), 0); - /* url */ - buf = "\r\nice-url: "; - send(sockfd, buf, strlen(buf), 0); - buf = bcurl; - send(sockfd, buf, strlen(buf), 0); - /* genre */ - buf = "\r\nice-genre: "; - send(sockfd, buf, strlen(buf), 0); - buf = bcgenre; - send(sockfd, buf, strlen(buf), 0); - /* public */ - buf = "\r\nice-public: "; - send(sockfd, buf, strlen(buf), 0); - if(bcpublic==0) /* set the public flag for broadcast */ - { - buf = "no"; - } - else - { - buf ="yes"; - } - send(sockfd, buf, strlen(buf), 0); - /* bitrate */ - buf = "\r\nice-bitrate: "; - send(sockfd, buf, strlen(buf), 0); - if(sprintf(resp, "%d", br_nom) == -1) /* convert int to a string */ - { - error("oggcast~: wrong bitrate"); - } - send(sockfd, resp, strlen(resp), 0); - /* description */ - buf = "\r\nice-description: "; - send(sockfd, buf, strlen(buf), 0); - buf = "ogg/vorbis streamed from pure-data with oggcast~"; - send(sockfd, buf, strlen(buf), 0); - /* end of header */ - buf = "\r\n\r\n"; - send(sockfd, buf, strlen(buf), 0); - /* end login for IceCast using ICE/1.0 scheme */ - } - else /* or try to log in at IceCast2 server using HTTP/1.0 base auth scheme */ - { - /* send the request, a string like: "SOURCE / HTTP/1.0\n\r" */ - buf = "SOURCE /"; - send(sockfd, buf, strlen(buf), 0); - buf = mountpoint; - send(sockfd, buf, strlen(buf), 0); - buf = " HTTP/1.0\r\n"; - send(sockfd, buf, strlen(buf), 0); - /* send basic authorization */ - sprintf(resp, "source:%s", passwd); - buf = oggcast_util_base64_encode(resp); - sprintf(resp, "Authorization: Basic %s\r\n", buf); - send(sockfd, resp, strlen(resp), 0); - /* send content type */ - buf = "Content-Type: application/x-ogg"; - send(sockfd, buf, strlen(buf), 0); - /* send the ice headers */ - /* password */ - buf = "\r\nice-password: "; - send(sockfd, buf, strlen(buf), 0); - buf = passwd; - send(sockfd, buf, strlen(buf), 0); - /* name */ - buf = "\r\nice-name: "; - send(sockfd, buf, strlen(buf), 0); - buf = bcname; - send(sockfd, buf, strlen(buf), 0); - /* url */ - buf = "\r\nice-url: "; - send(sockfd, buf, strlen(buf), 0); - buf = bcurl; - send(sockfd, buf, strlen(buf), 0); - /* genre */ - buf = "\r\nice-genre: "; - send(sockfd, buf, strlen(buf), 0); - buf = bcgenre; - send(sockfd, buf, strlen(buf), 0); - /* public */ - buf = "\r\nice-public: "; - send(sockfd, buf, strlen(buf), 0); - if(bcpublic==0) /* set the public flag for broadcast */ - { - buf = "0"; - } - else - { - buf = "1"; - } - send(sockfd, buf, strlen(buf), 0); - /* bitrate */ - buf = "\r\nice-bitrate: "; - send(sockfd, buf, strlen(buf), 0); - if(sprintf(resp, "%d", br_nom) == -1) /* convert int to a string */ - { - error("oggcast~: wrong bitrate"); - } - send(sockfd, resp, strlen(resp), 0); - /* description */ - buf = "\r\nice-description: "; - send(sockfd, buf, strlen(buf), 0); - buf = "Ogg/Vorbis streamed from Pure Data with oggcast~\r\n"; - send(sockfd, buf, strlen(buf), 0); - /* end of header: write an empty line */ - buf = "\r\n"; - send(sockfd, buf, strlen(buf), 0); - /* end login for IceCast2 using ICE/1.0 scheme */ - } - - /* check if we can write to server */ - if(oggcast_checkserver(sockfd)!= 0) - { - post("oggcast~: error: server refused to receive data"); - return (-1); - } - - post("oggcast~: logged in to http://%s:%d/%s", hp->h_name, portno, mountpoint); - - return (sockfd); -} - - - -static void oggcast_child_disconnect(t_int fd) -{ - sys_closesocket(fd); - post("oggcast~: 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 oggcast_cond_wait pthread_cond_wait -#define oggcast_cond_signal pthread_cond_signal -#else -#include /* debugging version... */ -#include -static void oggcast_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 oggcast_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 oggcast_cond_wait(a,b) oggcast_fakewait(b) -#define oggcast_cond_signal(a) -#endif - -static void *oggcast_child_main(void *zz) -{ - t_oggcast *x = zz; - time_t now; /* to get the time */ - 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"); - oggcast_cond_signal(&x->x_answercondition); - oggcast_cond_wait(&x->x_requestcondition, &x->x_mutex); - pute("3\n"); - } - // connect to Icecast2 server - else if (x->x_requestcode == REQUEST_CONNECT) - { - char boo[100]; - 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_br_nom; - t_int servertype = x->x_servertype; - /* 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 = oggcast_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 - { - /* get the time for the DATE comment */ - now=time(NULL); - x->x_bcdate = ctime(&now); - x->x_pages = 0; - clock_delay(x->x_clock_pages, 0); - /* initialise the encoder */ - if(oggcast_vorbis_init(x) == 0) - { - post("oggcast~: ogg/vorbis encoder initialised"); - x->x_eos = 0; - x->x_state = STATE_STREAM; - } - else - { - post("oggcast~: could not init encoder"); - oggcast_child_disconnect(fd); - post("oggcast~: connection closed due to initialisation error"); - x->x_fd = -1; - x->x_connectstate = 0; - clock_delay(x->x_clock_connect, 0); - pute("oggcast~: 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 * READ)); - /* 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 WRITESIZE bytes in the - buffer */ - if (x->x_fifohead < x->x_fifotail || - x->x_fifohead >= x->x_fifotail + READ - || (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 = oggcast_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("oggcast~: closing due to error..."); - goto lost; - } - else - { - x->x_fifotail += READ; - x->x_pages += 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"); - oggcast_cond_signal(&x->x_answercondition); - pute("signalled\n"); - oggcast_cond_wait(&x->x_requestcondition, - &x->x_mutex); - pute("7a done\n"); - continue; - } - /* signal parent in case it's waiting for data */ - oggcast_cond_signal(&x->x_answercondition); - } - } - - /* reinit encoder (settings have changed) */ - else if (x->x_requestcode == REQUEST_REINIT) - { - pthread_mutex_unlock(&x->x_mutex); - oggcast_vorbis_deinit(x); - oggcast_vorbis_init(x); - pthread_mutex_lock(&x->x_mutex); - post("oggcast~: ogg/vorbis encoder reinitialised"); - x->x_state = STATE_STREAM; - if (x->x_requestcode == REQUEST_REINIT) - x->x_requestcode = REQUEST_CONNECT; - oggcast_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); - oggcast_vorbis_deinit(x); - oggcast_child_disconnect(fd); - pthread_mutex_lock(&x->x_mutex); - x->x_fd = -1; - } - if (x->x_requestcode == REQUEST_CLOSE) - x->x_requestcode = REQUEST_NOTHING; - if (x->x_requestcode == REQUEST_BUSY) /* disconnect due to error */ - x->x_requestcode = REQUEST_NOTHING; - x->x_connectstate = 0; - clock_delay(x->x_clock_connect, 0); - x->x_eos = 1; - oggcast_cond_signal(&x->x_answercondition); - } - // 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); - oggcast_vorbis_deinit(x); - oggcast_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; - oggcast_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 oggcast_tick_connect(t_oggcast *x) -{ - pthread_mutex_lock(&x->x_mutex); - outlet_float(x->x_connection, x->x_connectstate); - pthread_mutex_unlock(&x->x_mutex); -} - -static void oggcast_tick_pages(t_oggcast *x) -{ - /* output new no. of pages if anything changed */ - t_float pages; - pthread_mutex_lock(&x->x_mutex); - pages = x->x_pages; /* get current value with mutex locked */ - pthread_mutex_unlock(&x->x_mutex); - if(pages != x->x_lastpages) - { - outlet_float(x->x_outpages, pages); - x->x_lastpages = pages; - } - clock_delay(x->x_clock_pages, UPDATE_INTERVAL); /* come back again... */ -} - -static void *oggcast_new(t_floatarg fnchannels, t_floatarg fbufsize) -{ - t_oggcast *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_oggcast *)pd_new(oggcast_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)oggcast_tick_connect); - x->x_clock_pages = clock_new(x, (t_method)oggcast_tick_pages); - - 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 */ - /* I found this to be most efficient on my machine */ - 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_resample.upsample = x->x_resample.downsample = 1; /* don't resample */ - - - x->x_fd = -1; - x->x_eos = 0; - x->x_vbr = 1; /* use the vbr setting by default */ - x->x_passwd = "letmein"; - x->x_samplerate = sys_getsr(); /* default to Pd's sampling rate */ - x->x_skip = 1; /* no resampling supported */ - x->x_quality = 0.4; /* quality 0.4 gives roughly 128kbps VBR stream */ - x->x_channels = nchannels; /* stereo */ - x->x_br_max = 144; - x->x_br_nom = 128; - x->x_br_min = 96; - x->x_pages = x->x_lastpages = 0; - x->x_bcname = "ogg/vorbis stream"; - x->x_bcurl = "http://www.akustische-kunst.org/puredata/"; - x->x_bcgenre = "experimental"; - x->x_bcdescription = "ogg/vorbis stream emitted from pure-data with oggcast~"; - x->x_bcartist = "Pd and oggcast~ v0.2"; - x->x_bclocation = x->x_bcurl; - x->x_bccopyright = ""; - x->x_bcperformer = ""; - x->x_bccontact = ""; - x->x_bcdate = ""; - x->x_bcpublic = 1; - x->x_mountpoint = "puredata.ogg"; - x->x_servertype = 1; /* HTTP/1.0 protocol for Icecast2 */ - - post(oggcast_version); - post("oggcast~: set buffer to %dk bytes", bufsize / 1024); - post("oggcast~: encoding %d channels @ %d Hz", x->x_channels, x->x_samplerate); - - clock_delay(x->x_clock_pages, 0); - pthread_create(&x->x_childthread, 0, oggcast_child_main, x); - return (x); -} - -static t_int *oggcast_perform(t_int *w) -{ - t_oggcast *x = (t_oggcast *)(w[1]); - int vecsize = x->x_vecsize, ninlets = x->x_ninlets, channels = x->x_channels, i, j, skip = x->x_skip; - float *sp = x->x_buf; - pthread_mutex_lock(&x->x_mutex); - if (x->x_state != STATE_IDLE) - { - int wantbytes; - /* get 'wantbytes' bytes from inlet */ - wantbytes = channels * vecsize / skip; /* we'll get vecsize bytes per channel */ - /* check if there is enough space in buffer to write all samples */ - while (x->x_fifotail > x->x_fifohead && - x->x_fifotail < x->x_fifohead + wantbytes + 1) - { - pute("wait...\n"); - oggcast_cond_signal(&x->x_requestcondition); - oggcast_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 += skip) - { - for(i = 0; i < channels; i++) - { - *sp++ = x->x_outvec[i][j]; - } - } - } - else if(channels == ninlets * 2) /* convert mono -> stereo */ - { - for(j = 0; j < vecsize; j += skip) - { - for(i = 0; i < ninlets; i++) - { - *sp++ = x->x_outvec[i][j]; - *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) - { - pute("signal 1\n"); - oggcast_cond_signal(&x->x_requestcondition); - x->x_sigcountdown = x->x_sigperiod; - } - } - - pthread_mutex_unlock(&x->x_mutex); - - return (w+2); -} - - -static void oggcast_disconnect(t_oggcast *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; - oggcast_cond_signal(&x->x_requestcondition); - } - else post("oggcast~: not connected"); - pthread_mutex_unlock(&x->x_mutex); -} - - - /* connect method. Called as: - connect - */ - -static void oggcast_connect(t_oggcast *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 >= 0) - { - post("oggcast~: already connected"); - } - else - { - x->x_requestcode = REQUEST_CONNECT; - x->x_hostname = hostsym->s_name; - x->x_mountpoint = mountsym->s_name; - x->x_port = portno; - - x->x_fifotail = 0; - x->x_fifohead = 0; - // if(x->x_recover != 1)x->x_fifobytes = 0; - - x->x_connecterror = 0; - x->x_state = STATE_STARTUP; - oggcast_cond_signal(&x->x_requestcondition); - } - pthread_mutex_unlock(&x->x_mutex); -} - -static void oggcast_float(t_oggcast *x, t_floatarg f) -{ - if (f != 0) - { - pthread_mutex_lock(&x->x_mutex); - if(x->x_fd >= 0) - { - post("oggcast~: already connected"); - } - else - { - if(x->x_recover != 1) - { - x->x_fifotail = 0; - x->x_fifohead = 0; - } - x->x_requestcode = REQUEST_CONNECT; - - x->x_fifotail = 0; - x->x_fifohead = 0; - - x->x_connecterror = 0; - x->x_state = STATE_STARTUP; - oggcast_cond_signal(&x->x_requestcondition); - } - pthread_mutex_unlock(&x->x_mutex); - } - else oggcast_disconnect(x); -} - -static void oggcast_dsp(t_oggcast *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(oggcast_perform, 1, x); -} - - /* set password for oggcast server */ -static void oggcast_password(t_oggcast *x, t_symbol *password) -{ - pthread_mutex_lock(&x->x_mutex); - x->x_passwd = password->s_name; - pthread_mutex_unlock(&x->x_mutex); -} - /* set comment fields for header (reads in just anything) */ -static void oggcast_comment(t_oggcast *x, t_symbol *s, t_int argc, t_atom* argv) -{ - t_binbuf *b = binbuf_new(); - char* comment; - int length; - binbuf_add(b, argc, argv); - binbuf_gettext(b, &comment, &length); - - pthread_mutex_lock(&x->x_mutex); - if(strstr(s->s_name, "ARTIST")) - { - strcpy(x->x_bcartist, comment); - post("oggcast~: ARTIST = %s", x->x_bcartist); - } - else if(strstr(s->s_name, "GENRE")) - { - strcpy(x->x_bcgenre, comment); - post("oggcast~: GENRE = %s", x->x_bcgenre); - } - else if(strstr(s->s_name, "TITLE")) - { - strcpy(x->x_bcname, comment); - post("oggcast~: TITLE = %s", x->x_bcname); - } - else if(strstr(s->s_name, "PERFORMER")) - { - strcpy(x->x_bcperformer, comment); - post("oggcast~: PERFORMER = %s", x->x_bcperformer); - } - else if(strstr(s->s_name, "LOCATION")) - { - strcpy(x->x_bclocation, comment); - post("oggcast~: LOCATION = %s", x->x_bclocation); - } - else if(strstr(s->s_name, "COPYRIGHT")) - { - strcpy(x->x_bccopyright, comment); - post("oggcast~: COPYRIGHT = %s", x->x_bccopyright); - } - else if(strstr(s->s_name, "CONTACT")) - { - strcpy(x->x_bccontact, comment); - post("oggcast~: CONTACT = %s", x->x_bccontact); - } - else if(strstr(s->s_name, "DESCRIPTION")) - { - strcpy(x->x_bcdescription, comment); - post("oggcast~: DESCRIPTION = %s", x->x_bcdescription); - } - else if(strstr(s->s_name, "DATE")) - { - strcpy(x->x_bcdate, comment); - post("oggcast~: DATE = %s", x->x_bcdate); - } - else post("oggcast~: no method for %s", s->s_name); - if(x->x_state == STATE_STREAM) - { - x->x_state = STATE_IDLE; - x->x_requestcode = REQUEST_REINIT; - oggcast_cond_signal(&x->x_requestcondition); - } - pthread_mutex_unlock(&x->x_mutex); - freebytes(comment, strlen(comment)); -} - /* settings for variable bitrate encoding */ -static void oggcast_vbr(t_oggcast *x, t_floatarg fsr, t_floatarg fchannels, - t_floatarg fquality) -{ - pthread_mutex_lock(&x->x_mutex); - x->x_vbr = 1; - x->x_samplerate = (t_int)fsr; - x->x_quality = fquality; - x->x_channels = (t_int)fchannels; - post("oggcast~: %d channels @ %d Hz, quality %.2f", x->x_channels, x->x_samplerate, x->x_quality); - if(x->x_state == STATE_STREAM) - { - x->x_state = STATE_IDLE; - x->x_requestcode = REQUEST_REINIT; - oggcast_cond_signal(&x->x_requestcondition); - } - pthread_mutex_unlock(&x->x_mutex); -} - - /* settings for bitrate-based vbr encoding */ -static void oggcast_vorbis(t_oggcast *x, t_floatarg fsr, t_floatarg fchannels, - t_floatarg fmax, t_floatarg fnom, t_floatarg fmin) -{ - pthread_mutex_lock(&x->x_mutex); - x->x_vbr = 0; - x->x_samplerate = (t_int)fsr; - x->x_channels = (t_int)fchannels; - x->x_br_max = (t_int)fmax; - x->x_br_nom = (t_int)fnom; - x->x_br_min = (t_int)fmin; - post("oggcast~: %d channels @ %d Hz, bitrates: max. %d / nom. %d / min. %d", - x->x_channels, x->x_samplerate, x->x_br_max, x->x_br_nom, x->x_br_min); - if(x->x_state == STATE_STREAM) - { - x->x_state = STATE_IDLE; - x->x_requestcode = REQUEST_REINIT; - oggcast_cond_signal(&x->x_requestcondition); - } - pthread_mutex_unlock(&x->x_mutex); -} - /* select server type */ -static void oggcast_server(t_oggcast *x, t_floatarg f) -{ - pthread_mutex_lock(&x->x_mutex); - if(f) - { - x->x_servertype = 1; - post("oggcast~: set server type to new Icecast2 (HTTP/1.0 scheme)"); - } - else - { - x->x_servertype = 0; - post("oggcast~: set server type to JRoar (ICE/1.0 scheme)"); - } - pthread_mutex_unlock(&x->x_mutex); -} - /* print settings to pd's console window */ -static void oggcast_print(t_oggcast *x) -{ - pthread_mutex_lock(&x->x_mutex); - if(x->x_servertype) - post("oggcast~: server type is Icecast2"); - else - post("oggcast~: server type is JRoar"); - post("oggcast~: mountpoint at Icecast2: %s", x->x_mountpoint); - if(x->x_vbr == 1) - { - post("oggcast~: Ogg Vorbis encoder: %d channels @ %d Hz, quality %.2f", x->x_channels, x->x_samplerate, x->x_quality); - } - else - { - post("oggcast~: Ogg Vorbis encoder: %d channels @ %d Hz, bitrates: max. %d, nom. %d, min. %d", - x->x_channels, x->x_samplerate, x->x_br_max, x->x_br_nom, x->x_br_min); - } - post("oggcast~: Ogg Vorbis comments:"); - post(" TITLE = %s", x->x_bcname); - post(" ARTIST = %s", x->x_bcartist); - post(" PERFORMER = %s", x->x_bcperformer); - post(" GENRE = %s", x->x_bcgenre); - post(" LOCATION = %s", x->x_bclocation); - post(" COPYRIGHT = %s", x->x_bccopyright); - post(" CONTACT = %s", x->x_bccontact); - post(" DESCRIPTION = %s", x->x_bcdescription); - post(" DATE = %s", x->x_bcdate); - pthread_mutex_unlock(&x->x_mutex); -} - - -static void oggcast_free(t_oggcast *x) -{ - /* request QUIT and wait for acknowledge */ - void *threadrtn; - pthread_mutex_lock(&x->x_mutex); - x->x_requestcode = REQUEST_QUIT; - post("stopping oggcast thread..."); - oggcast_cond_signal(&x->x_requestcondition); - while (x->x_requestcode != REQUEST_NOTHING) - { - post("signalling..."); - oggcast_cond_signal(&x->x_requestcondition); - oggcast_cond_wait(&x->x_answercondition, &x->x_mutex); - } - pthread_mutex_unlock(&x->x_mutex); - if (pthread_join(x->x_childthread, &threadrtn)) - error("oggcast_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)); - clock_free(x->x_clock_connect); - clock_free(x->x_clock_pages); -} - -void oggcast_tilde_setup(void) -{ - oggcast_class = class_new(gensym("oggcast~"), (t_newmethod)oggcast_new, - (t_method)oggcast_free, sizeof(t_oggcast), 0, A_DEFFLOAT, A_DEFFLOAT, 0); - CLASS_MAINSIGNALIN(oggcast_class, t_oggcast, x_f ); - class_addfloat(oggcast_class, (t_method)oggcast_float); - class_addmethod(oggcast_class, (t_method)oggcast_disconnect, gensym("disconnect"), 0); - class_addmethod(oggcast_class, (t_method)oggcast_dsp, gensym("dsp"), 0); - class_addmethod(oggcast_class, (t_method)oggcast_connect, gensym("connect"), A_GIMME, 0); - class_addmethod(oggcast_class, (t_method)oggcast_print, gensym("print"), 0); - class_addmethod(oggcast_class, (t_method)oggcast_password, gensym("passwd"), A_SYMBOL, 0); - class_addmethod(oggcast_class, (t_method)oggcast_vorbis, gensym("vorbis"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0); - class_addmethod(oggcast_class, (t_method)oggcast_vbr, gensym("vbr"), A_FLOAT, A_FLOAT, A_FLOAT, 0); - class_addmethod(oggcast_class, (t_method)oggcast_server, gensym("server"), A_FLOAT, 0); - class_addanything(oggcast_class, oggcast_comment); - class_sethelpsymbol(oggcast_class, gensym("help-oggcast~.pd")); -} +/* ------------------------- oggcast~ ----------------------------------------- */ +/* */ +/* Tilde object to send an Ogg Vorbis stream from to IceCast2 server. */ +/* Written by Olaf Matthes (olaf.matthes@gmx.de) */ +/* Get source at http://www.akustische-kunst.org/puredata/pdogg/ */ +/* */ +/* 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 Ogg Vorbis decoding library which can be found at */ +/* http://www.vorbis.com/ */ +/* */ +/* ---------------------------------------------------------------------------- */ + + /* Pd includes */ +#include "m_pd.h" +#include "g_canvas.h" + /* Vorbis includes */ +#include "codec.h" +#include "vorbisenc.h" + +#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 + +/************************* oggcast~ object ******************************/ + +/* Each instance of oggcast~ 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 aund audio output */ + +#define READ 4096 /* amount of data we pass on to encoder */ +#define DEFBUFPERCHAN 262144 /* audio output buffer by default: 256k */ +#define MINBUFSIZE 65536 +#define MAXBUFSIZE 16777216 /* arbitrary; just don't want to hang malloc */ +#define STRBUF_SIZE 1024 /* char received from server on startup */ +#define MAXSTREAMCHANS 256 /* maximum number of channels: restricted by Pd? */ +#define UPDATE_INTERVAL 250 /* time in milliseconds between updates of output values */ + +#ifdef __linux__ // 'real' linux only, not for OS X ! +#define SEND_OPT MSG_DONTWAIT|MSG_NOSIGNAL +#else +#define SEND_OPT 0 +#endif + + +static char *oggcast_version = "oggcast~: ogg/vorbis streaming client version 0.2k, written by Olaf Matthes"; + +static t_class *oggcast_class; + +typedef struct _oggcast +{ + 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; /* 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) */ + + /* ogg/vorbis related stuff */ + ogg_stream_state x_os; /* take physical pages, weld into a logical stream of packets */ + ogg_page x_og; /* one Ogg bitstream page. Vorbis packets are inside */ + ogg_packet x_op; /* one raw packet of data for decode */ + vorbis_info x_vi; /* struct that stores all the static vorbis bitstream settings */ + vorbis_comment x_vc; /* struct that stores all the user comments */ + vorbis_dsp_state x_vd; /* central working state for the packet->PCM decoder */ + vorbis_block x_vb; /* local working space for packet->PCM decode */ + + t_int x_eos; /* end of stream */ + t_float x_pages; /* number of pages that have been output to server */ + t_float x_lastpages; + + /* ringbuffer stuff */ + t_float *x_buffer; /* data to be buffered (ringbuffer)*/ + t_int x_bytesbuffered; /* number of unprocessed bytes in buffer */ + + /* ogg/vorbis format stuff */ + t_int x_samplerate; /* samplerate of stream (default = getsr() ) */ + t_int x_skip; /* samples from input to skip (for resampling) */ + t_float x_quality; /* desired quality level from 0.0 to 1.0 (lo to hi) */ + t_int x_br_max; /* max. bitrate of ogg/vorbis stream */ + t_int x_br_nom; /* nom. bitrate of ogg/vorbis stream */ + t_int x_br_min; /* min. bitrate of ogg/vorbis stream */ + t_int x_channels; /* number of channels (1 or 2) */ + t_int x_vbr; + + /* 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_bcdescription; /* description */ + char* x_bcartist; /* artist */ + char* x_bclocation; + char* x_bccopyright; + char* x_bcperformer; + char* x_bccontact; + char* x_bcdate; /* system date when broadcast started */ + 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.oggcast.com */ + t_int x_servertype; /* type of server: 0 = JRoar or old Icecast2; 1 = new Icecast2 */ + + + + + 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 */ + t_resample x_resample; /* resampling unit */ + t_int x_recover; /* indicate how to behave on buffer underruns */ + + /* thread stuff */ + pthread_mutex_t x_mutex; + pthread_cond_t x_requestcondition; + pthread_cond_t x_answercondition; + pthread_t x_childthread; +} t_oggcast; + + +static char base64table[65] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/', +}; + + /* This isn't efficient, but it doesn't need to be */ +char *oggcast_util_base64_encode(char *data) +{ + int len = strlen(data); + char *out = (char *)getbytes(len*4/3 + 4); + char *result = out; + int chunk; + + while(len > 0) { + chunk = (len >3)?3:len; + *out++ = base64table[(*data & 0xFC)>>2]; + *out++ = base64table[((*data & 0x03)<<4) | ((*(data+1) & 0xF0) >> 4)]; + + switch(chunk) { + case 3: + *out++ = base64table[((*(data+1) & 0x0F)<<2) | ((*(data+2) & 0xC0)>>6)]; + *out++ = base64table[(*(data+2)) & 0x3F]; + break; + case 2: + *out++ = base64table[((*(data+1) & 0x0F)<<2)]; + *out++ = '='; + break; + case 1: + *out++ = '='; + *out++ = '='; + break; + } + data += chunk; + len -= chunk; + } + *out = 0; + + return result; +} + + /* check server for writeability */ +static int oggcast_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("oggcast~: can not write data to the server, quitting"); + return -1; + } + if(FD_ISSET(sock, &exceptset)) + { + post("oggcast~: socket returned an error, quitting"); + return -1; + } + } + return 0; +} + + /* stream ogg/vorbis to IceCast2 server */ +static int oggcast_stream(t_oggcast *x, t_int fd) +{ + int err = -1; /* error return code */ + int pages = 0; + + /* write out pages (if any) */ + while(!x->x_eos) + { + int result=ogg_stream_pageout(&(x->x_os),&(x->x_og)); + if(result==0)break; + err = send(fd, x->x_og.header, x->x_og.header_len, SEND_OPT); + if(err < 0) + { + error("oggcast~: could not send ogg header to server (%d)", err); + x->x_eos = 1; /* indicate (artificial) end of stream */ + return err; + } + err = send(fd, x->x_og.body, x->x_og.body_len, SEND_OPT); + if(err < 0) + { + error("oggcast~: could not send ogg body to server (%d)", err); + x->x_eos = 1; /* indicate (artificial) end of stream */ + return err; + } + pages++; /* count number of pages */ + /* there might be more than one pages we have to send */ + if(ogg_page_eos(&(x->x_og)))x->x_eos=1; + } + return (pages); +} + + /* ogg/vorbis decoder setup */ +static int oggcast_vorbis_init(t_oggcast *x) +{ + int err = -1; + + x->x_eos = 0; + x->x_skip = 1; /* assume no resampling */ + /* choose an encoding mode */ + vorbis_info_init(&(x->x_vi)); + + if(x->x_samplerate != sys_getsr()) /* downsampling for Oliver (http://radiostudio.org) */ + { + if(sys_getsr() / x->x_samplerate == 2.0) + { + post("oggcast~: downsampling from %.0f to %d Hz", sys_getsr(), x->x_samplerate); + x->x_skip = 2; + } + else if(sys_getsr() / x->x_samplerate == 4.0) + { + post("oggcast~: downsampling from %.0f to %d Hz", sys_getsr(), x->x_samplerate); + x->x_skip = 4; + } + else if(sys_getsr() / x->x_samplerate == 3.0) + { + post("oggcast~: downsampling from %.0f to %d Hz", sys_getsr(), x->x_samplerate); + x->x_skip = 3; + } else post("oggcast~: warning: resampling from %.0f to %d not supported", sys_getsr(), x->x_samplerate); + } + if(x->x_vbr == 1) + { /* quality based setting */ + if(vorbis_encode_init_vbr(&(x->x_vi), x->x_channels, x->x_samplerate, x->x_quality)) + { + post("oggcast~: ogg/vorbis mode initialisation failed: invalid parameters for quality"); + vorbis_info_clear(&(x->x_vi)); + return (-1); + } + } + else + { /* bitrate based setting */ + if(vorbis_encode_init(&(x->x_vi), x->x_channels, x->x_samplerate, x->x_br_max*1024, x->x_br_nom*1024, x->x_br_min*1024)) + { + post("oggcast~: ogg/vorbis mode initialisation failed: invalid parameters for quality"); + vorbis_info_clear(&(x->x_vi)); + return (-1); + } + } + + /* add a comment */ + vorbis_comment_init(&(x->x_vc)); + vorbis_comment_add_tag(&(x->x_vc),"TITLE", x->x_bcname); + vorbis_comment_add_tag(&(x->x_vc),"ARTIST", x->x_bcartist); + vorbis_comment_add_tag(&(x->x_vc),"GENRE",x->x_bcgenre); + vorbis_comment_add_tag(&(x->x_vc),"DESCRIPTION", x->x_bcdescription); + vorbis_comment_add_tag(&(x->x_vc),"LOCATION",x->x_bclocation); + vorbis_comment_add_tag(&(x->x_vc),"PERFORMER",x->x_bcperformer); + vorbis_comment_add_tag(&(x->x_vc),"COPYRIGHT",x->x_bccopyright); + vorbis_comment_add_tag(&(x->x_vc),"CONTACT",x->x_bccontact); + vorbis_comment_add_tag(&(x->x_vc),"DATE",x->x_bcdate); + vorbis_comment_add_tag(&(x->x_vc),"ENCODER","oggcast~ v0.2 for pure-data"); + + /* set up the analysis state and auxiliary encoding storage */ + vorbis_analysis_init(&(x->x_vd),&(x->x_vi)); + vorbis_block_init(&(x->x_vd),&(x->x_vb)); + + /* set up our packet->stream encoder */ + /* pick a random serial number; that way we can more likely build + chained streams just by concatenation */ + srand(time(NULL)); + ogg_stream_init(&(x->x_os),rand()); + + /* Vorbis streams begin with three headers; the initial header (with + most of the codec setup parameters) which is mandated by the Ogg + bitstream spec. The second header holds any comment fields. The + third header holds the bitstream codebook. We merely need to + make the headers, then pass them to libvorbis one at a time; + libvorbis handles the additional Ogg bitstream constraints */ + + { + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; + + vorbis_analysis_headerout(&(x->x_vd),&(x->x_vc),&header,&header_comm,&header_code); + ogg_stream_packetin(&(x->x_os),&header); /* automatically placed in its own page */ + ogg_stream_packetin(&(x->x_os),&header_comm); + ogg_stream_packetin(&(x->x_os),&header_code); + + /* We don't have to write out here, but doing so makes streaming + * much easier, so we do, flushing ALL pages. This ensures the actual + * audio data will start on a new page + * + * IceCast2 server will take this as a first info about our stream + */ + while(!x->x_eos) + { + int result=ogg_stream_flush(&(x->x_os),&(x->x_og)); + if(result==0)break; + err = send(x->x_fd, x->x_og.header, x->x_og.header_len, SEND_OPT); + if(err < 0) + { + error("oggcast~: could not send ogg header to server (%d)", err); + x->x_eos = 1; /* indicate end of stream */ + return (-1); + } + err = send(x->x_fd, x->x_og.body, x->x_og.body_len, SEND_OPT); + if(err < 0) + { + error("oggcast~: could not send ogg body to server (%d)", err); + x->x_eos = 1; /* indicate end of stream */ + return (-1); + } + } + } + return (0); +} + + /* deinit the ogg/vorbis decoder */ +static void oggcast_vorbis_deinit(t_oggcast *x) +{ + vorbis_analysis_wrote(&(x->x_vd),0); + /* get rid of remaining data in encoder, if any */ + while(vorbis_analysis_blockout(&(x->x_vd),&(x->x_vb))==1) + { + vorbis_analysis(&(x->x_vb),NULL); + vorbis_bitrate_addblock(&(x->x_vb)); + + while(vorbis_bitrate_flushpacket(&(x->x_vd),&(x->x_op))) + { + ogg_stream_packetin(&(x->x_os),&(x->x_op)); + oggcast_stream(x, x->x_fd); + } + } + /* clean up and exit. vorbis_info_clear() must be called last */ + ogg_stream_clear(&(x->x_os)); + vorbis_block_clear(&(x->x_vb)); + vorbis_dsp_clear(&(x->x_vd)); + vorbis_comment_clear(&(x->x_vc)); + vorbis_info_clear(&(x->x_vi)); +} + + /* encode ogg/vorbis and stream new data */ +static int oggcast_encode(t_oggcast *x, float *buf, int channels, int fifosize, int fd) +{ + unsigned short i, ch; + int err = 0; + int n, pages = 0; + + /* expose the buffer to submit data */ + float **inbuffer=vorbis_analysis_buffer(&(x->x_vd),READ * channels); + + /* read from buffer */ + for(n = 0; n < READ; n++) /* fill encode buffer */ + { + for(ch = 0; ch < channels; ch++) + { + inbuffer[ch][n] = *buf++; + } + } + /* tell the library how much we actually submitted */ + vorbis_analysis_wrote(&(x->x_vd),n); + + /* vorbis does some data preanalysis, then divvies up blocks for + more involved (potentially parallel) processing. Get a single + block for encoding now */ + while(vorbis_analysis_blockout(&(x->x_vd),&(x->x_vb))==1) + { + /* analysis, assume we want to use bitrate management */ + vorbis_analysis(&(x->x_vb),NULL); + vorbis_bitrate_addblock(&(x->x_vb)); + + while(vorbis_bitrate_flushpacket(&(x->x_vd),&(x->x_op))) + { + /* weld the packet into the bitstream */ + ogg_stream_packetin(&(x->x_os),&(x->x_op)); + err = oggcast_stream(x, fd); /* stream packet to server */ + if(err >= 0) + { + pages += err; /* count pages */ + } + else return (err); + } + } + return (pages); +} + + /* connect to icecast2 server */ +static int oggcast_child_connect(char *hostname, char *mountpoint, t_int portno, + char *passwd, char *bcname, char *bcurl, + char *bcgenre, t_int bcpublic, t_int br_nom, t_int servertype) +{ + 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; + t_int sockfd; /* our internal handle for the socket */ + t_int ret; + + sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) + { + error("oggcast~: 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("oggcast~: 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("oggcast~: connecting to port %d", portno); + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + error("oggcast~: 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("oggcast~: can not read from socket"); + sys_closesocket(sockfd); + return (-1); + } + + post("oggcast~: logging in to IceCast2 server..."); + /* now try to log in at IceCast2 server using ICE/1.0 scheme */ + if(servertype == 0) + { + /* send the request, a string like: "SOURCE / ICE/1.0\n" */ + buf = "SOURCE "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = "/"; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = mountpoint; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = " ICE/1.0"; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* send the ice headers */ + /* password */ + buf = "\nice-password: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = passwd; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* name */ + buf = "\r\nice-name: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = bcname; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* url */ + buf = "\r\nice-url: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = bcurl; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* genre */ + buf = "\r\nice-genre: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = bcgenre; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* public */ + buf = "\r\nice-public: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + if(bcpublic==0) /* set the public flag for broadcast */ + { + buf = "no"; + } + else + { + buf ="yes"; + } + send(sockfd, buf, strlen(buf), SEND_OPT); + /* bitrate */ + buf = "\r\nice-bitrate: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + if(sprintf(resp, "%d", br_nom) == -1) /* convert int to a string */ + { + error("oggcast~: wrong bitrate"); + } + send(sockfd, resp, strlen(resp), SEND_OPT); + /* description */ + buf = "\r\nice-description: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = "ogg/vorbis streamed from pure-data with oggcast~"; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* end of header */ + buf = "\r\n\r\n"; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* end login for IceCast using ICE/1.0 scheme */ + } + else /* or try to log in at IceCast2 server using HTTP/1.0 base auth scheme */ + { + /* send the request, a string like: "SOURCE / HTTP/1.0\n\r" */ + buf = "SOURCE /"; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = mountpoint; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = " HTTP/1.0\r\n"; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* send basic authorization */ + sprintf(resp, "source:%s", passwd); + buf = oggcast_util_base64_encode(resp); + sprintf(resp, "Authorization: Basic %s\r\n", buf); + send(sockfd, resp, strlen(resp), SEND_OPT); + /* send content type */ + buf = "Content-Type: application/x-ogg"; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* send the ice headers */ + /* password */ + buf = "\r\nice-password: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = passwd; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* name */ + buf = "\r\nice-name: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = bcname; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* url */ + buf = "\r\nice-url: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = bcurl; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* genre */ + buf = "\r\nice-genre: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = bcgenre; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* public */ + buf = "\r\nice-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); + /* bitrate */ + buf = "\r\nice-bitrate: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + if(sprintf(resp, "%d", br_nom) == -1) /* convert int to a string */ + { + error("oggcast~: wrong bitrate"); + } + send(sockfd, resp, strlen(resp), SEND_OPT); + /* description */ + buf = "\r\nice-description: "; + send(sockfd, buf, strlen(buf), SEND_OPT); + buf = "Ogg/Vorbis streamed from PureData with oggcast~\r\n"; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* end of header: write an empty line */ + buf = "\r\n"; + send(sockfd, buf, strlen(buf), SEND_OPT); + /* end login for IceCast2 using ICE/1.0 scheme */ + } + + /* check if we can write to server */ + if(oggcast_checkserver(sockfd)!= 0) + { + post("oggcast~: error: server refused to receive data"); + return (-1); + } + + post("oggcast~: logged in to http://%s:%d/%s", hp->h_name, portno, mountpoint); + + return (sockfd); +} + + + +static void oggcast_child_disconnect(t_int fd) +{ + sys_closesocket(fd); + post("oggcast~: 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 oggcast_cond_wait pthread_cond_wait +#define oggcast_cond_signal pthread_cond_signal +#else +#include /* debugging version... */ +#include +static void oggcast_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 oggcast_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 oggcast_cond_wait(a,b) oggcast_fakewait(b) +#define oggcast_cond_signal(a) +#endif + +static void *oggcast_child_main(void *zz) +{ + t_oggcast *x = zz; + time_t now; /* to get the time */ + 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"); + oggcast_cond_signal(&x->x_answercondition); + oggcast_cond_wait(&x->x_requestcondition, &x->x_mutex); + pute("3\n"); + } + // connect to Icecast2 server + else if (x->x_requestcode == REQUEST_CONNECT) + { + char boo[100]; + 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_br_nom; + t_int servertype = x->x_servertype; + /* 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 = oggcast_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 + { + /* get the time for the DATE comment */ + now=time(NULL); + x->x_bcdate = ctime(&now); + x->x_pages = 0; + clock_delay(x->x_clock_pages, 0); + /* initialise the encoder */ + if(oggcast_vorbis_init(x) == 0) + { + post("oggcast~: ogg/vorbis encoder initialised"); + x->x_eos = 0; + x->x_state = STATE_STREAM; + } + else + { + post("oggcast~: could not init encoder"); + oggcast_child_disconnect(fd); + post("oggcast~: connection closed due to initialisation error"); + x->x_fd = -1; + x->x_connectstate = 0; + clock_delay(x->x_clock_connect, 0); + pute("oggcast~: 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 * READ)); + /* 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 WRITESIZE bytes in the + buffer */ + if (x->x_fifohead < x->x_fifotail || + x->x_fifohead >= x->x_fifotail + (READ * x->x_channels) + || (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 = oggcast_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("oggcast~: closing due to error..."); + goto lost; + } + else + { + x->x_fifotail += (READ * x->x_channels); + x->x_pages += 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"); + oggcast_cond_signal(&x->x_answercondition); + pute("signalled\n"); + oggcast_cond_wait(&x->x_requestcondition, + &x->x_mutex); + pute("7a done\n"); + continue; + } + /* signal parent in case it's waiting for data */ + oggcast_cond_signal(&x->x_answercondition); + } + } + + /* reinit encoder (settings have changed) */ + else if (x->x_requestcode == REQUEST_REINIT) + { + pthread_mutex_unlock(&x->x_mutex); + oggcast_vorbis_deinit(x); + oggcast_vorbis_init(x); + pthread_mutex_lock(&x->x_mutex); + post("oggcast~: ogg/vorbis encoder reinitialised"); + x->x_state = STATE_STREAM; + if (x->x_requestcode == REQUEST_REINIT) + x->x_requestcode = REQUEST_CONNECT; + oggcast_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); + oggcast_vorbis_deinit(x); + oggcast_child_disconnect(fd); + pthread_mutex_lock(&x->x_mutex); + x->x_fd = -1; + } + if (x->x_requestcode == REQUEST_CLOSE) + x->x_requestcode = REQUEST_NOTHING; + if (x->x_requestcode == REQUEST_BUSY) /* disconnect due to error */ + x->x_requestcode = REQUEST_NOTHING; + x->x_connectstate = 0; + clock_delay(x->x_clock_connect, 0); + x->x_eos = 1; + oggcast_cond_signal(&x->x_answercondition); + } + // 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); + oggcast_vorbis_deinit(x); + oggcast_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; + oggcast_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 oggcast_tick_connect(t_oggcast *x) +{ + pthread_mutex_lock(&x->x_mutex); + outlet_float(x->x_connection, x->x_connectstate); + pthread_mutex_unlock(&x->x_mutex); +} + +static void oggcast_tick_pages(t_oggcast *x) +{ + /* output new no. of pages if anything changed */ + t_float pages; + pthread_mutex_lock(&x->x_mutex); + pages = x->x_pages; /* get current value with mutex locked */ + pthread_mutex_unlock(&x->x_mutex); + if(pages != x->x_lastpages) + { + outlet_float(x->x_outpages, pages); + x->x_lastpages = pages; + } + clock_delay(x->x_clock_pages, UPDATE_INTERVAL); /* come back again... */ +} + +static void *oggcast_new(t_floatarg fnchannels, t_floatarg fbufsize) +{ + t_oggcast *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_oggcast *)pd_new(oggcast_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_outvec = getbytes(nchannels*sizeof(t_sample *)); + x->x_clock_connect = clock_new(x, (t_method)oggcast_tick_connect); + x->x_clock_pages = clock_new(x, (t_method)oggcast_tick_pages); + + 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 */ + /* I found this to be most efficient on my machine */ + 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_resample.upsample = x->x_resample.downsample = 1; /* don't resample */ + + + x->x_fd = -1; + x->x_eos = 0; + x->x_vbr = 1; /* use the vbr setting by default */ + x->x_passwd = "letmein"; + x->x_samplerate = sys_getsr(); /* default to Pd's sampling rate */ + x->x_skip = 1; /* no resampling supported */ + x->x_quality = 0.4; /* quality 0.4 gives roughly 128kbps VBR stream */ + x->x_channels = nchannels; /* stereo */ + x->x_br_max = 144; + x->x_br_nom = 128; + x->x_br_min = 96; + x->x_pages = x->x_lastpages = 0; + x->x_bcname = "ogg/vorbis stream"; + x->x_bcurl = "http://www.akustische-kunst.org/puredata/"; + x->x_bcgenre = "experimental"; + x->x_bcdescription = "ogg/vorbis stream emitted from pure-data with oggcast~"; + x->x_bcartist = "Pd and oggcast~ v0.2"; + x->x_bclocation = x->x_bcurl; + x->x_bccopyright = ""; + x->x_bcperformer = ""; + x->x_bccontact = ""; + x->x_bcdate = ""; + x->x_bcpublic = 1; + x->x_mountpoint = "puredata.ogg"; + x->x_servertype = 1; /* HTTP/1.0 protocol for Icecast2 */ + + post(oggcast_version); + post("oggcast~: set buffer to %dk bytes", bufsize / 1024); + post("oggcast~: encoding %d channels @ %d Hz", x->x_channels, x->x_samplerate); + + clock_delay(x->x_clock_pages, 0); + pthread_create(&x->x_childthread, 0, oggcast_child_main, x); + return (x); +} + +static t_int *oggcast_perform(t_int *w) +{ + t_oggcast *x = (t_oggcast *)(w[1]); + int vecsize = x->x_vecsize, ninlets = x->x_ninlets, channels = x->x_channels, i, j, skip = x->x_skip; + float *sp = x->x_buf; + pthread_mutex_lock(&x->x_mutex); + if (x->x_state != STATE_IDLE) + { + int wantbytes; + /* get 'wantbytes' bytes from inlet */ + wantbytes = channels * vecsize / skip; /* we'll get vecsize bytes per channel */ + /* check if there is enough space in buffer to write all samples */ + while (x->x_fifotail > x->x_fifohead && + x->x_fifotail < x->x_fifohead + wantbytes + 1) + { + pute("wait...\n"); + oggcast_cond_signal(&x->x_requestcondition); + oggcast_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 += skip) + { + for(i = 0; i < channels; i++) + { + *sp++ = x->x_outvec[i][j]; + } + } + } + else if(channels == ninlets * 2) /* convert mono -> stereo */ + { + for(j = 0; j < vecsize; j += skip) + { + for(i = 0; i < ninlets; i++) + { + *sp++ = x->x_outvec[i][j]; + *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) + { + pute("signal 1\n"); + oggcast_cond_signal(&x->x_requestcondition); + x->x_sigcountdown = x->x_sigperiod; + } + } + + pthread_mutex_unlock(&x->x_mutex); + + return (w+2); +} + + +static void oggcast_disconnect(t_oggcast *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; + oggcast_cond_signal(&x->x_requestcondition); + } + else post("oggcast~: not connected"); + pthread_mutex_unlock(&x->x_mutex); +} + + + /* connect method. Called as: + connect + */ + +static void oggcast_connect(t_oggcast *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 >= 0) + { + post("oggcast~: already connected"); + } + else + { + x->x_requestcode = REQUEST_CONNECT; + x->x_hostname = hostsym->s_name; + x->x_mountpoint = mountsym->s_name; + x->x_port = portno; + + x->x_fifotail = 0; + x->x_fifohead = 0; + // if(x->x_recover != 1)x->x_fifobytes = 0; + + x->x_connecterror = 0; + x->x_state = STATE_STARTUP; + oggcast_cond_signal(&x->x_requestcondition); + } + pthread_mutex_unlock(&x->x_mutex); +} + +static void oggcast_float(t_oggcast *x, t_floatarg f) +{ + if (f != 0) + { + pthread_mutex_lock(&x->x_mutex); + if(x->x_fd >= 0) + { + post("oggcast~: already connected"); + } + else + { + if(x->x_recover != 1) + { + x->x_fifotail = 0; + x->x_fifohead = 0; + } + x->x_requestcode = REQUEST_CONNECT; + + x->x_fifotail = 0; + x->x_fifohead = 0; + + x->x_connecterror = 0; + x->x_state = STATE_STARTUP; + oggcast_cond_signal(&x->x_requestcondition); + } + pthread_mutex_unlock(&x->x_mutex); + } + else oggcast_disconnect(x); +} + +static void oggcast_dsp(t_oggcast *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(oggcast_perform, 1, x); +} + + /* set password for oggcast server */ +static void oggcast_password(t_oggcast *x, t_symbol *password) +{ + pthread_mutex_lock(&x->x_mutex); + x->x_passwd = password->s_name; + pthread_mutex_unlock(&x->x_mutex); +} + /* set comment fields for header (reads in just anything) */ +static void oggcast_comment(t_oggcast *x, t_symbol *s, t_int argc, t_atom* argv) +{ + t_binbuf *b = binbuf_new(); + char* comment; + int length; + binbuf_add(b, argc, argv); + binbuf_gettext(b, &comment, &length); + + pthread_mutex_lock(&x->x_mutex); + if(strstr(s->s_name, "ARTIST")) + { + strcpy(x->x_bcartist, comment); + post("oggcast~: ARTIST = %s", x->x_bcartist); + } + else if(strstr(s->s_name, "GENRE")) + { + strcpy(x->x_bcgenre, comment); + post("oggcast~: GENRE = %s", x->x_bcgenre); + } + else if(strstr(s->s_name, "TITLE")) + { + strcpy(x->x_bcname, comment); + post("oggcast~: TITLE = %s", x->x_bcname); + } + else if(strstr(s->s_name, "PERFORMER")) + { + strcpy(x->x_bcperformer, comment); + post("oggcast~: PERFORMER = %s", x->x_bcperformer); + } + else if(strstr(s->s_name, "LOCATION")) + { + strcpy(x->x_bclocation, comment); + post("oggcast~: LOCATION = %s", x->x_bclocation); + } + else if(strstr(s->s_name, "COPYRIGHT")) + { + strcpy(x->x_bccopyright, comment); + post("oggcast~: COPYRIGHT = %s", x->x_bccopyright); + } + else if(strstr(s->s_name, "CONTACT")) + { + strcpy(x->x_bccontact, comment); + post("oggcast~: CONTACT = %s", x->x_bccontact); + } + else if(strstr(s->s_name, "DESCRIPTION")) + { + strcpy(x->x_bcdescription, comment); + post("oggcast~: DESCRIPTION = %s", x->x_bcdescription); + } + else if(strstr(s->s_name, "DATE")) + { + strcpy(x->x_bcdate, comment); + post("oggcast~: DATE = %s", x->x_bcdate); + } + else post("oggcast~: no method for %s", s->s_name); + if(x->x_state == STATE_STREAM) + { + x->x_state = STATE_IDLE; + x->x_requestcode = REQUEST_REINIT; + oggcast_cond_signal(&x->x_requestcondition); + } + pthread_mutex_unlock(&x->x_mutex); + freebytes(comment, strlen(comment)); + binbuf_free(b); +} + /* settings for variable bitrate encoding */ +static void oggcast_vbr(t_oggcast *x, t_floatarg fsr, t_floatarg fchannels, + t_floatarg fquality) +{ + pthread_mutex_lock(&x->x_mutex); + x->x_vbr = 1; + x->x_samplerate = (t_int)fsr; + x->x_quality = fquality; + x->x_channels = (t_int)fchannels; + post("oggcast~: %d channels @ %d Hz, quality %.2f", x->x_channels, x->x_samplerate, x->x_quality); + if(x->x_state == STATE_STREAM) + { + x->x_state = STATE_IDLE; + x->x_requestcode = REQUEST_REINIT; + oggcast_cond_signal(&x->x_requestcondition); + } + pthread_mutex_unlock(&x->x_mutex); +} + + /* settings for bitrate-based vbr encoding */ +static void oggcast_vorbis(t_oggcast *x, t_floatarg fsr, t_floatarg fchannels, + t_floatarg fmax, t_floatarg fnom, t_floatarg fmin) +{ + pthread_mutex_lock(&x->x_mutex); + x->x_vbr = 0; + x->x_samplerate = (t_int)fsr; + x->x_channels = (t_int)fchannels; + x->x_br_max = (t_int)fmax; + x->x_br_nom = (t_int)fnom; + x->x_br_min = (t_int)fmin; + post("oggcast~: %d channels @ %d Hz, bitrates: max. %d / nom. %d / min. %d", + x->x_channels, x->x_samplerate, x->x_br_max, x->x_br_nom, x->x_br_min); + if(x->x_state == STATE_STREAM) + { + x->x_state = STATE_IDLE; + x->x_requestcode = REQUEST_REINIT; + oggcast_cond_signal(&x->x_requestcondition); + } + pthread_mutex_unlock(&x->x_mutex); +} + /* select server type */ +static void oggcast_server(t_oggcast *x, t_floatarg f) +{ + pthread_mutex_lock(&x->x_mutex); + if(f) + { + x->x_servertype = 1; + post("oggcast~: set server type to new Icecast2 (HTTP/1.0 scheme)"); + } + else + { + x->x_servertype = 0; + post("oggcast~: set server type to JRoar (ICE/1.0 scheme)"); + } + pthread_mutex_unlock(&x->x_mutex); +} + /* print settings to pd's console window */ +static void oggcast_print(t_oggcast *x) +{ + pthread_mutex_lock(&x->x_mutex); + if(x->x_servertype) + post("oggcast~: server type is Icecast2"); + else + post("oggcast~: server type is JRoar"); + post("oggcast~: mountpoint at Icecast2: %s", x->x_mountpoint); + if(x->x_vbr == 1) + { + post("oggcast~: Ogg Vorbis encoder: %d channels @ %d Hz, quality %.2f", x->x_channels, x->x_samplerate, x->x_quality); + } + else + { + post("oggcast~: Ogg Vorbis encoder: %d channels @ %d Hz, bitrates: max. %d, nom. %d, min. %d", + x->x_channels, x->x_samplerate, x->x_br_max, x->x_br_nom, x->x_br_min); + } + post("oggcast~: Ogg Vorbis comments:"); + post(" TITLE = %s", x->x_bcname); + post(" ARTIST = %s", x->x_bcartist); + post(" PERFORMER = %s", x->x_bcperformer); + post(" GENRE = %s", x->x_bcgenre); + post(" LOCATION = %s", x->x_bclocation); + post(" COPYRIGHT = %s", x->x_bccopyright); + post(" CONTACT = %s", x->x_bccontact); + post(" DESCRIPTION = %s", x->x_bcdescription); + post(" DATE = %s", x->x_bcdate); + pthread_mutex_unlock(&x->x_mutex); +} + + +static void oggcast_free(t_oggcast *x) +{ + /* request QUIT and wait for acknowledge */ + void *threadrtn; + pthread_mutex_lock(&x->x_mutex); + x->x_requestcode = REQUEST_QUIT; + post("stopping oggcast thread..."); + oggcast_cond_signal(&x->x_requestcondition); + while (x->x_requestcode != REQUEST_NOTHING) + { + post("signalling..."); + oggcast_cond_signal(&x->x_requestcondition); + oggcast_cond_wait(&x->x_answercondition, &x->x_mutex); + } + pthread_mutex_unlock(&x->x_mutex); + if (pthread_join(x->x_childthread, &threadrtn)) + error("oggcast_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_outvec, x->x_ninlets*sizeof(t_sample *)); + clock_free(x->x_clock_connect); + clock_free(x->x_clock_pages); +} + +void oggcast_tilde_setup(void) +{ + oggcast_class = class_new(gensym("oggcast~"), (t_newmethod)oggcast_new, + (t_method)oggcast_free, sizeof(t_oggcast), 0, A_DEFFLOAT, A_DEFFLOAT, 0); + CLASS_MAINSIGNALIN(oggcast_class, t_oggcast, x_f ); + class_addfloat(oggcast_class, (t_method)oggcast_float); + class_addmethod(oggcast_class, (t_method)oggcast_disconnect, gensym("disconnect"), 0); + class_addmethod(oggcast_class, (t_method)oggcast_dsp, gensym("dsp"), 0); + class_addmethod(oggcast_class, (t_method)oggcast_connect, gensym("connect"), A_GIMME, 0); + class_addmethod(oggcast_class, (t_method)oggcast_print, gensym("print"), 0); + class_addmethod(oggcast_class, (t_method)oggcast_password, gensym("passwd"), A_SYMBOL, 0); + class_addmethod(oggcast_class, (t_method)oggcast_vorbis, gensym("vorbis"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0); + class_addmethod(oggcast_class, (t_method)oggcast_vbr, gensym("vbr"), A_FLOAT, A_FLOAT, A_FLOAT, 0); + class_addmethod(oggcast_class, (t_method)oggcast_server, gensym("server"), A_FLOAT, 0); + class_addanything(oggcast_class, oggcast_comment); + class_sethelpsymbol(oggcast_class, gensym("help-oggcast~.pd")); +} \ No newline at end of file -- cgit v1.2.1