/* ------------------------ mp3streamin~ -------------------------------------- */ /* */ /* Tilde object to receive an mp3-stream sent by a peer using mp3streamout~. */ /* Written by Yves Degoyon (ydegoyon@free.fr). */ /* Tarballs and updates @ http://ydegoyon.free.fr */ /* */ /* This program is free software; you can redistribute it and/or */ /* modify it under the terms of the GNU General Public License */ /* as published by the Free Software Foundation; either version 2 */ /* of the License, or (at your option) any later version. */ /* */ /* See file LICENSE for further informations on licensing terms. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* */ /* Based on PureData by Miller Puckette and others. */ /* Uses the LAME MPEG 1 Layer 3 encoding library (lame_enc.dll) which can */ /* be found at http://www.cdex.n3.net. */ /* */ /* "Repackage sex, your interests." */ /* "Somehow, maintain the interest." */ /* Gang Of Four -- Natural's Not In It */ /* ---------------------------------------------------------------------------- */ #include #include #include #if PD_MINOR_VERSION >=37 #include "s_stuff.h" #endif #include #include #ifdef _WIN32 #include #include #else #include #include #include #include #include #include #include #include #include #include #define SOCKET_ERROR -1 #endif /* _WIN32 */ #if defined(__APPLE__) || defined(_WIN32) #define MSG_NOSIGNAL 0 #define SOL_TCP IPPROTO_TCP #endif #include "mpg123.h" /* mpg123 decoding library from lame 3.92 */ #include "mpglib.h" /* mpglib decoding library from lame 3.92 */ #include "interface.h" /* mpglib decoding library from lame 3.92 */ #define MIN_AUDIO_INPUT 8064 // we must a least have 8 chunks to play a correct sound #define INPUT_BUFFER_SIZE MIN_AUDIO_INPUT #define OUTPUT_BUFFER_SIZE 131072 /* 128k*/ #define LAME_AUDIO_CHUNK_SIZE 1152 #define BARHEIGHT 10 #define MAX_DECODERS 10 /* useful debugging functions from mpglib */ extern int decode_header( struct frame* fr, unsigned long newhead ); extern void print_header_compact( struct frame* fr ); extern int head_check( unsigned long head, int check_layer ); /* time-out used for select() call */ static struct timeval ztout; static char *mp3streamin_version = "mp3streamin~: mp3 peer-to-peer streamer version 0.3, written by ydegoyon@free.fr"; extern void sys_sockerror(char *s); void mp3streamin_closesocket(int fd) { #ifndef _MSC_VER if ( close(fd) < 0 ) { perror( "close" ); } else { post( "mp3streamin~ : closed socket : %d", fd ); } #endif #ifdef _WIN32 closesocket(fd); #endif sys_rmpollfn(fd); } int setsocketoptions(int sockfd) { int sockopt = 1; if (setsockopt(sockfd, SOL_TCP, TCP_NODELAY, (const char*) &sockopt, sizeof(int)) < 0) { post("mp3streamin~ : setsockopt TCP_NODELAY failed"); perror( "setsockopt" ); return -1; } else { post("mp3streamin~ : TCP_NODELAY set"); } #ifndef _MSC_VER sockopt = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(int)) < 0) { post("mp3streamin~ : setsockopt SO_REUSEADDR failed"); perror( "setsockopt" ); return -1; } else { post("mp3streamin~ : setsockopt SO_REUSEADDR done."); } #endif return 0; } /* ------------------------ mp3streamin~ ----------------------------- */ static t_class *mp3streamin_class; typedef struct _mp3streamin { t_object x_obj; t_int x_instance; t_int x_socket; t_int x_shutdown; t_outlet *x_connectionip; t_int x_serversocket; t_int x_inpackets; /* number of packets received */ t_int x_dpacket; /* displayed packet in status bar */ t_int x_packetsize; /* size of the packets */ t_int x_pblocks; /* processed blocks */ t_int x_graphic; /* indicates if we show a graphic bar */ void *x_inbuffer; /* accumulation buffer for incoming mp3 frames */ t_int x_inwriteposition; t_int x_inbuffersize; t_float *x_outbuffer; /* buffer to store audio decoded data */ t_int x_outwriteposition; t_int x_outreadposition; t_int x_outunread; t_int x_outbuffersize; t_canvas *x_canvas; t_int x_stream; /* indicates if a stream is connected ( meaning correct input flow ) */ t_int x_newstream; /* at first, the stream must provide enough data to start */ t_int x_bitrateindex; /* remember the bitrate index */ } t_mp3streamin; /* too bad, this needs to be static, handling an array to enable several decoders in pd */ static MPSTR mp[MAX_DECODERS]; /* decoder buffer */ int nbinstances = 0; void mp3streamin_tilde_mpglib_init(t_mp3streamin *x) { int ret; InitMP3(&mp[x->x_instance]); } static int mp3streamin_decode_input(t_mp3streamin *x) { int i; int alength = 0; struct frame hframe; unsigned int a,b,c,d; unsigned long cheader; static char out[8192]; signed short int *p = (signed short int *) out; int pbytes; int ret; if ( !x->x_shutdown ) { /* decode first 4 bytes as the header */ a = *((unsigned char*)x->x_inbuffer); b = *((unsigned char*)x->x_inbuffer+1); c = *((unsigned char*)x->x_inbuffer+2); d = *((unsigned char*)x->x_inbuffer+3); cheader = 0; cheader = a; cheader <<= 8; cheader |= b; cheader <<= 8; cheader |= c; cheader <<= 8; cheader |= d; if ( head_check( cheader, 0 ) ) { // post( "mp3streamin~ : valid header ( packet=%d)", x->x_inpackets ); decode_header( &hframe, cheader ); // print_header_compact( &hframe ); // when the bitrate change, reinit decoder if ( x->x_bitrateindex != hframe.bitrate_index ) { ExitMP3(&mp[x->x_instance]); InitMP3(&mp[x->x_instance]); x->x_bitrateindex = hframe.bitrate_index; } } else { post( "mp3streamin~ : error : mp3 packet received without header" ); // ignore data x->x_inwriteposition = 0; x->x_inpackets--; return( -1 ); } // post( "mp3streamin~ : decoding %d bytes framesize=%d", x->x_inwriteposition, hframe.framesize ); ret = decodeMP3(&mp[x->x_instance], (unsigned char*)x->x_inbuffer, x->x_inwriteposition, (char *) p, sizeof(out), &pbytes); switch (ret) { case MP3_OK: switch (mp[x->x_instance].fr.stereo) { case 1: case 2: alength = ((mp[x->x_instance].fr.stereo==1)?pbytes >> 1 : pbytes >> 2); // post( "mp3streamin~ : processed %d samples", alength ); // update outbuffer contents for ( i=0; ix_outunread >= x->x_outbuffersize-2 ) { post( "mp3streamin~ : too much input ... ignored" ); continue; } *(x->x_outbuffer+x->x_outwriteposition) = ((t_float)(*p++))/32767.0; x->x_outwriteposition = (x->x_outwriteposition + 1)%x->x_outbuffersize; *(x->x_outbuffer+x->x_outwriteposition) = ((mp[x->x_instance].fr.stereo==2)?((t_float)(*p++))/32767.0 : 0.0); x->x_outwriteposition = (x->x_outwriteposition + 1)%x->x_outbuffersize; x->x_outunread+=2; if ( x->x_outunread > MIN_AUDIO_INPUT && !x->x_stream ) { post( "mp3streamin~ : stream connected." ); x->x_stream = 1; } } break; default: alength = -1; break; } break; case MP3_NEED_MORE: post( "mp3streamin~ : retry lame decoding (more data needed)." ); alength = 0; break; case MP3_ERR: post( "mp3streamin~ : lame decoding failed." ); alength = -1; break; } x->x_inwriteposition = 0; } else { if ( x->x_outunread == 0 ) { post( "mp3streamin~ : connection closed" ); mp3streamin_closesocket(x->x_socket); x->x_stream = 0; x->x_newstream = 0; x->x_inpackets = 0; x->x_inwriteposition = 0; x->x_bitrateindex = -1; x->x_socket=-1; outlet_symbol( x->x_connectionip, gensym("") ); } } if ( x->x_graphic && glist_isvisible( x->x_canvas ) ) { /* update graphical read status */ if ( x->x_inpackets != x->x_dpacket ) { char color[32]; int minpackets = ( MIN_AUDIO_INPUT/LAME_AUDIO_CHUNK_SIZE )-2; // audio loop has eaten some already sys_vgui(".x%lx.c delete rectangle %xSTATUS\n", x->x_canvas, x ); sys_vgui(".x%lx.c delete line %xTHRESHOLD\n", x->x_canvas, x ); if ( x->x_outunread > 0 ) { t_int width; if ( x->x_inpackets < (MIN_AUDIO_INPUT/LAME_AUDIO_CHUNK_SIZE)/2 ) { strcpy( color, "red" ); } else { strcpy( color, "lightgreen" ); } width = rtext_width( glist_findrtext( (t_glist*)x->x_canvas, (t_text *)x ) ); sys_vgui(".x%lx.c create rectangle %d %d %d %d -fill %s -tags %xSTATUS\n", x->x_canvas, x->x_obj.te_xpix, x->x_obj.te_ypix-BARHEIGHT-1, x->x_obj.te_xpix+(x->x_inpackets*x->x_packetsize*width)/INPUT_BUFFER_SIZE, x->x_obj.te_ypix - 1, color, x ); sys_vgui(".x%lx.c create line %d %d %d %d -fill red -tags %xTHRESHOLD\n", x->x_canvas, x->x_obj.te_xpix+(minpackets*x->x_packetsize*width)/INPUT_BUFFER_SIZE, x->x_obj.te_ypix-BARHEIGHT-1, x->x_obj.te_xpix+(minpackets*x->x_packetsize*width)/INPUT_BUFFER_SIZE, x->x_obj.te_ypix-1, x ); x->x_dpacket = x->x_inpackets; } else { if ( x->x_shutdown ) { x->x_shutdown=0; sys_vgui(".x%lx.c delete rectangle %xPBAR\n", x->x_canvas, x ); sys_vgui(".x%lx.c delete line %xTHRESHOLD\n", x->x_canvas, x ); } } } } return alength; } static void mp3streamin_recv(t_mp3streamin *x) { int ret; if ( ( ret = recv(x->x_socket, (void*) (x->x_inbuffer + x->x_inwriteposition), (size_t)((x->x_inbuffersize-x->x_inwriteposition)), MSG_NOSIGNAL) ) < 0 ) { post( "mp3_streamin~ : receive error" ); perror( "recv" ); return; } else { // post( "streamin~ : received %d bytes at %d on %d ( up to %d)", // ret, x->x_inwriteposition, x->x_socket, // x->x_inbuffersize-x->x_inwriteposition*sizeof( unsigned long) ); if ( ret == 0 ) { /* initiate the shutdown phase */ x->x_shutdown=1; } else { // check we don't overflow input buffer if ( (x->x_inpackets+1)*x->x_packetsize > x->x_inbuffersize ) { post( "mp3streamin~ : too much input...resetting" ); x->x_inpackets=0; x->x_inwriteposition=0; return; } x->x_inpackets++; x->x_packetsize=ret; if ( x->x_inpackets % 100 == 0 ) { // post( "mp3streamin~ : received %d packets", x->x_inpackets ); } x->x_inwriteposition += ret; } mp3streamin_decode_input(x); } } static void mp3streamin_acceptconnection(t_mp3streamin *x) { struct sockaddr_in incomer_address; int sockaddrl = (int) sizeof( struct sockaddr ); int fd = accept(x->x_serversocket, (struct sockaddr*)&incomer_address, &sockaddrl ); if (fd < 0) { post("mp3streamin~: accept failed"); return; } if (x->x_socket > 0) { sys_addpollfn(fd, (t_fdpollfn)mp3streamin_recv, x); if ( x->x_outunread != 0 ) { post("mp3streamin~: still have some data to decode, retry later you %s.", inet_ntoa( incomer_address.sin_addr )); mp3streamin_closesocket( fd ); return; } post("mp3streamin~: the source has changed to %s.", inet_ntoa( incomer_address.sin_addr )); mp3streamin_closesocket(x->x_socket); } x->x_socket = fd; sys_addpollfn(x->x_socket, (t_fdpollfn)mp3streamin_recv, x); outlet_symbol( x->x_connectionip, gensym( inet_ntoa( incomer_address.sin_addr) ) ); if ( x->x_graphic && glist_isvisible( x->x_canvas ) ) { t_int width; width = rtext_width( glist_findrtext( (t_glist*)x->x_canvas, (t_text *)x ) ); sys_vgui(".x%lx.c create rectangle %d %d %d %d -fill lightblue -tags %xPBAR\n", x->x_canvas, x->x_obj.te_xpix, x->x_obj.te_ypix-BARHEIGHT-1, x->x_obj.te_xpix + width, x->x_obj.te_ypix - 1, x ); } x->x_stream = 0; x->x_newstream = 1; } static int mp3streamin_startservice(t_mp3streamin* x, int portno) { struct sockaddr_in server; int sockfd; /* create a socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { sys_sockerror("socket"); return (0); } server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; /* assign server port number */ server.sin_port = htons((u_short)portno); post("listening to port number %d", portno); setsocketoptions(sockfd); /* name the socket */ if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) { sys_sockerror("bind"); mp3streamin_closesocket(sockfd); return (0); } if (listen(sockfd, 5) < 0) { sys_sockerror("listen"); mp3streamin_closesocket(sockfd); } else { x->x_serversocket = sockfd; sys_addpollfn(x->x_serversocket, (t_fdpollfn)mp3streamin_acceptconnection, x); } return 1; } static void mp3streamin_free(t_mp3streamin *x) { post( "mp3streamin~ : free %x", x ); if (x->x_serversocket > 0) { post( "mp3streamin~ : closing server socket" ); mp3streamin_closesocket(x->x_serversocket); x->x_serversocket = -1; } if (x->x_socket > 0) { post( "mp3streamin~ : closing socket" ); mp3streamin_closesocket(x->x_socket); x->x_socket = -1; } if ( x->x_inbuffer ) freebytes( x->x_inbuffer, x->x_inbuffersize ); if ( x->x_outbuffer ) freebytes( x->x_outbuffer, x->x_outbuffersize*sizeof(t_float) ); if ( x->x_instance == nbinstances-1 ) { nbinstances--; } } static t_int *mp3streamin_perform(t_int *w) { t_mp3streamin *x = (t_mp3streamin*) (w[1]); t_float *out1 = (t_float *)(w[2]); t_float *out2 = (t_float *)(w[3]); int n = (int)(w[4]); int bsize = n; int ret; int i = 0; while( n-- ) { if ( ( ( x->x_outunread > MIN_AUDIO_INPUT ) && x->x_newstream ) || // wait the buffer to load ( ( x->x_shutdown ) && ( x->x_outunread >= 2 ) ) || // clean disconnection ( x->x_stream ) // check that the stream provides enough data ) { if ( x->x_newstream && !x->x_shutdown ) { x->x_newstream = 0; x->x_stream = 1; } *out1++=*(x->x_outbuffer+x->x_outreadposition); x->x_outreadposition = (x->x_outreadposition + 1)%x->x_outbuffersize; *out2++=*(x->x_outbuffer+x->x_outreadposition); x->x_outreadposition = (x->x_outreadposition + 1)%x->x_outbuffersize; x->x_outunread-=2; if ( n == 1 ) x->x_pblocks++; } else { *out1++=0.0; *out2++=0.0; } } if ( ( x->x_outunread <= MIN_AUDIO_INPUT/10 ) && ( x->x_stream ) ) { post( "mp3streamin~ : stream lost (too little input)" ); x->x_stream = 0; x->x_newstream = 1; // waiting for a new stream } if ( x->x_pblocks == LAME_AUDIO_CHUNK_SIZE/bsize ) { x->x_inpackets--; x->x_pblocks = 0; } #ifdef DO_MY_OWN_SELECT // check new incoming data if ( x->x_socket > 0 ) { fd_set readset; fd_set exceptset; FD_ZERO(&readset); FD_ZERO(&exceptset); FD_SET(x->x_socket, &readset ); FD_SET(x->x_socket, &exceptset ); if ( select( maxfd+1, &readset, NULL, &exceptset, &ztout ) >0 ) { if ( FD_ISSET( x->x_socket, &readset) || FD_ISSET( x->x_socket, &exceptset ) ) { /* receive data or error and decode it */ mp3streamin_recv(x); } } } #endif return (w+5); } static void mp3streamin_dsp(t_mp3streamin *x, t_signal **sp) { dsp_add(mp3streamin_perform, 4, x, sp[0]->s_vec, sp[1]->s_vec, sp[0]->s_n); } static void *mp3streamin_new(t_floatarg fportno, t_floatarg fdographics) { t_mp3streamin *x; int i; if ( fportno < 0 || fportno > 65535 ) { post( "mp3streamin~ : error : wrong portnumber : %d", (int)fportno ); return NULL; } if ( ((int)fdographics != 0) && ((int)fdographics != 1.) ) { post( "mp3streamin~ : error : constructor : mp3streamin~ [graphic flag = 0 | 1 ] ( got = %f)", fdographics ); return NULL; } x = (t_mp3streamin *)pd_new(mp3streamin_class); post( "mp3streamin~ : new %x (instance = %d) %d", x, nbinstances, sizeof( MPSTR ) ); outlet_new(&x->x_obj, &s_signal); outlet_new(&x->x_obj, &s_signal); x->x_connectionip = outlet_new(&x->x_obj, &s_symbol); x->x_serversocket = -1; x->x_socket = -1; x->x_shutdown = 0; x->x_inpackets = 0; x->x_dpacket = -1; x->x_canvas = canvas_getcurrent(); x->x_inbuffersize = INPUT_BUFFER_SIZE; x->x_outbuffersize = OUTPUT_BUFFER_SIZE; x->x_inbuffer = (char*) getbytes( x->x_inbuffersize ); memset( x->x_inbuffer, 0x0, INPUT_BUFFER_SIZE ); x->x_outbuffer = (t_float*) getbytes( x->x_outbuffersize*sizeof(t_float) ); memset( x->x_outbuffer, 0x0, OUTPUT_BUFFER_SIZE ); if ( !x->x_inbuffer || !x->x_outbuffer ) { post( "mp3streamin~ : could not allocate buffers." ); return NULL; } if ( nbinstances < MAX_DECODERS ) { x->x_instance = nbinstances++; } else { post( "mp3streamin~ : cannot create more decoders (memory issues), sorry" ); return NULL; } x->x_inwriteposition = 0; x->x_outreadposition = 0; x->x_outwriteposition = 0; x->x_outunread = 0; ztout.tv_sec = 0; ztout.tv_usec = 0; x->x_graphic = (int)fdographics; post( "mp3streamin~ : starting service on port %d", (int)fportno ); mp3streamin_startservice(x, (int)fportno); // init lame decoder mp3streamin_tilde_mpglib_init(x); return (x); } void mp3streamin_tilde_setup(void) { logpost(NULL, 4, "%s", mp3streamin_version ); mp3streamin_class = class_new(gensym("mp3streamin~"), (t_newmethod) mp3streamin_new, (t_method) mp3streamin_free, sizeof(t_mp3streamin), CLASS_NOINLET, A_DEFFLOAT, A_DEFFLOAT, A_NULL); class_addmethod(mp3streamin_class, nullfn, gensym("signal"), 0); class_addmethod(mp3streamin_class, (t_method) mp3streamin_dsp, gensym("dsp"), 0); }