/* $Id: muse.cpp,v 1.17 2004/12/15 18:18:06 jaromil Exp $ */ /** @mainpage MuSE :: Multiple Streaming Engine @section Introduction MuSE is an application for mixing, encoding, and network streaming of sound. It relies code that has been written with modularity and reusability in mind. Actually the engine can play MP3 or OGG files, mix them together up to an amount of 6 input streams and then reencode them together and stream them to the network again. It can also produce multiple encoded streams at the same time, and save the encoded audio to a local file. Streams produced by MuSE can run on various servers: icecast 1 and 2, litestream, darwin (with icecast emulation), shoutcast and theoretically any other protocol supported by libshout. MuSE can read its own MP3 streams (and mix them, and restream them, and...), but also a large number of audio players can do: xmms, freeamp, winamp, itunes, winzozz media player and probably more. In its application form, MuSE offers two allready implemented interfaces to be operated in realtime and a slick commandline interface. This documentation is useful to who wants to reuse or tweak MuSE's code, if you are not a programmer (and you don't want to become one) you are not really interested in all the details documented here. Nevertless, reading here might give you a picture about some unexplored possibility of audio streaming ;) A good place to start from is the Stream_mixer class, which is the main interface to interact and send asynchronous commands to the engine while its running. In fact the API declared in the jmixer.h file is the one used by the interactive user interfaces. A useful implementation example can be found in muse.cpp MuSE engine is being developed and hereby documented in the hope to provide the Free Software community with user friendly tool for network audio streaming and a high level interface for programming automatic radio tools. @section Authors the MuSE Engine is Copyright (C) 2000-2004 Denis Rojo aka jaromil - http://rastasoft.org the GTK-2 MuSE interface is Copyright (C) 2002-2004 Antonio Radici aka nightolo - http://freaknet.org the Ncurses MuSE interface is Copyright (C) 2002-2004 Luca Profico aka rubik - http://olografix.org MuSE and all its interface source code is free software; you can redistribute it and/or modify it under the terms of the GNU Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. MuSE source code 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. Please refer to the GNU Public License for more details. You should have received a copy of the GNU Public License along with this source code; if not, write to: Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef GUI_NIGHTOLO #include #endif #ifdef GUI_NIGHTOLO2 #include #endif #ifdef GUI_RUBIK #include #endif #ifdef UI_XMLRPC #include #endif #ifdef HAVE_SCHEDULER #include "radiosched.h" #endif /* command line stuff */ char *version = "%s version %s http://muse.dyne.org"; char *help = "Usage: muse [generic options] [-e [encoder options] [stream options] ] [files]\n" ":: generic options:\n" " -h --help this help\n" " -v --version version information\n" " -D --debug [1-3] debug verbosity level - default 1\n" " -o --dspout disable souncard output - default on\n" " -C --cli command line input (no GUI)\n" " -g --gui specify GUI to use (-g list)\n" ":: input channels options\n" " -i --live mic/line soundcard input - default off\n" " -I --liveamp mic/line volume (1 - 32) - default 1\n" " -N --number channel number - default 1\n" " -V --volume channel volume - default 1.0\n" " -S --position channel starting position - default 0.0\n" " -P --playmode playmode: play, cont, loop - default cont\n" ":: output encoders options:\n" " -e --encoder codec to use [ogg|mp3] - default ogg\n" " -b --bitrate codec bitrate in Kbit/s - default 24\n" " -r --frequency encoding frequency - default auto\n" " -q --quality encoding quality (1-9) - default 4\n" " -c --channels number of audio channels - default 1\n" " -f --filedump dump stream to file\n" ":: broadcast stream options:\n" " -s --server stream to server[:port] - default port 8000\n" " -m --mount mounpoint on server - default live\n" " -l --login login type [ice1|ice2|icy] - default ice1\n" " -p --pass encoder password on server\n" " -n --name name of the stream\n" " -u --url descriptive url of the stream\n" " -d --desc description of the stream\n" "\n"; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { "debug", required_argument, NULL, 'D' }, { "live", no_argument, NULL, 'i' }, { "liveamp", required_argument, NULL, 'I' }, { "dspout", no_argument, NULL, 'o' }, { "cli", no_argument, NULL, 'C' }, { "number", required_argument, NULL, 'N' }, { "volume", required_argument, NULL, 'V' }, { "position", required_argument, NULL, 'S' }, { "playmode", required_argument, NULL, 'P' }, { "encoder", required_argument, NULL, 'e' }, { "bitrate", required_argument, NULL, 'b' }, { "frequency", required_argument, NULL, 'r' }, { "quality", required_argument, NULL, 'q' }, { "channels", required_argument, NULL, 'c' }, { "filedump", required_argument, NULL, 'f' }, { "gui", required_argument, NULL, 'g' }, { "server", required_argument, NULL, 's' }, { "port", required_argument, NULL, 'p' }, { "mount", required_argument, NULL, 'm' }, { "login", required_argument, NULL, 'l' }, { "pass",required_argument, NULL, 'p' }, { "name",required_argument, NULL, 'n' }, { "url",required_argument, NULL, 'u' }, { "desc", required_argument, NULL, 'd' }, { 0, 0, 0, 0 } }; char *short_options = "-hvD:ioCN:V:S:P:e:b:r:q:c:f:g:s:m:l:p:n:u:d:"; /* misc settings */ #define MAX_CLI_CHARS 9182 int debug = 1; bool daemon_mode = false; char *queue_file = NULL; int lfreq = 0; float quality = 1.0f; int channels = 1; int thegui = -1; /* no gui */ enum interface { CLI, GTK1, GTK2, NCURSES }; // channel options int number = 0; int playmode = PLAYMODE_CONT; bool has_playlist = false; bool dspout = true; bool micrec = false; int micvol = 16; bool snddev = false; Stream_mixer *mix = NULL; GUI *gui = NULL; OutChannel *outch = NULL; int encid = 0; Shouter *ice = NULL; int iceid = 0; /* declare the sighandlers */ void quitproc (int Sig); void fsigpipe (int Sig); bool got_sigpipe; /* ---------------------- */ bool take_args(int argc, char **argv) { int res; MuseSetDebug(1); do { res = getopt_long(argc, argv, short_options, long_options, NULL); switch(res) { case 'h': fprintf(stderr,"%s",help); exit(0); case 'v': notice("GTK2 GUI by Antonino \"nightolo\" Radici "); notice("Ncurses console by Luca \"rubik\" Profico "); act(" "); notice("BIG UP \\o/ dyne.org / FreakNet / RASTASOFT / Metro Olografix"); act(" "); act("part of the redistributed code is copyright by the respective authors,"); act("please refer to the AUTHORS file and to the sourcecode for complete"); act("information."); act(" "); act("This source code is free software; you can redistribute it and/or"); act("modify it under the terms of the GNU Public License as published"); act("by the Free Software Foundation; either version 2 of the License,"); act("or (at your option) any later version."); act(" "); act("This source code is distributed in the hope that it will be useful,"); act("but WITHOUT ANY WARRANTY; without even the implied warranty of"); act("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."); act("Please refer to the GNU Public License for more details."); act(" "); act("You should have received a copy of the GNU Public License along with"); act("this source code; if not, write to:"); act("Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA."); exit(0); case 'D': MuseSetDebug( atoi(optarg) ); break; case 'o': /* if( !mix->set_lineout(false) ) error("soundcard not present"); else { act("CLI: soundcard disabled"); dspout = false; } */ dspout = false; break; case 'i': /* if( !mix->set_live(true) ) error("soundcard not present"); else { act("CLI: recording from mic/linein"); micrec = true; } */ micrec = true; mix->set_mic_volume(micvol); notice("CLI: recording from mic/linein"); break; case 'I': micvol = atoi(optarg); if(micvol < 1 || micvol > 32) error("invalid mic/line volume value, must be a approx between 1 and 32"); else { mix->set_mic_volume(micvol); act("CLI: mic/linein volume gain %i",micvol); } break; case 'e': encid = 0; #ifdef HAVE_VORBIS if (strncasecmp("ogg",optarg,3) == 0) { encid = mix->create_enc(OGG); notice("CLI: created Ogg encoder"); } #endif #ifdef HAVE_LAME if (strncasecmp("mp3",optarg,3) == 0) { encid = mix->create_enc(MP3); notice("CLI: created Mp3 encoder"); } #endif if(encid>0) outch = mix->get_enc(encid); if(outch) break; error("you can specify an encoder with the -e option"); act("supported encoders are:"); #ifdef HAVE_VORBIS act(" OGG - Ogg/Vorbis codec"); #endif #ifdef HAVE_LAME act(" MP3 - Lame MP3 codec"); #endif exit(0); case 'b': if(!outch) { error("invalid command line argument: bps"); error("you must specify a codec first with the -e option"); break; } outch->bps( atoi(optarg) ); act("CLI: bitrate set to %iKbit/s",outch->bps()); break; case 'r': if(!outch) { error("invalid command line argument: frequency"); error("you must specify a codec first with the -e option"); break; } lfreq = atoi(optarg); if(lfreq != 0 && lfreq != 11000 && lfreq != 16000 && lfreq != 22050 && lfreq != 32000 && lfreq != 44100) { error("invalid frequency %i",lfreq); error("must be 0, 11000, 16000, 22050, 32000 or 44100 Hz!"); act("CLI: falling back to auto"); lfreq = 0; } outch->freq(lfreq); act("CLI: frequency set to %iKhz",outch->freq()); break; case 'q': if(!outch) { error("invalid command line argument: quality"); error("you must specify a codec first with the -e option"); break; } if( sscanf(optarg,"%f",&quality) ) { /* if(quality<0.1f) quality = 0.1f; if(quality>9.0f) quality = 9.0f; */ // outch->quality(quality); act("CLI: quality set to %s",outch->quality(quality)); } else error("invalid quality value"); break; case 'c': if(!outch) { error("invalid command line argument: channels"); error("you must specify a codec first with the -e option"); break; } channels = atoi(optarg); if(channels>2 | channels<1) { error("audio channels can be only 1 (mono) or 2 (stereo)"); act("falling back to default: 1 (mono)"); channels = 1; } outch->channels(channels); act("CLI: encoding %i channel(s)",channels); break; case 'f': if(!outch) { error("invalid command line argument: file dump"); error("you must specify a codec first with the -e option"); break; } outch->dump_start( optarg ); act("CLI: file saving to %s",optarg); break; case 'C': thegui = 0; break; case 'N': number = atoi(optarg); number = (number<0) ? 0 : (number>=MAX_CHANNELS) ? MAX_CHANNELS-1 : number; // number--; if(!mix->chan[number]) { if(!mix->create_channel(number)) { error("got problems creating channel %i",number); } else { notice("CLI: created channel %i",number); mix->set_playmode(number,playmode); } } break; case 'V': float vol; if(sscanf(optarg,"%f",&vol)==EOF) error("CLI: invalid volume for channel %i",number); else { vol = (vol>1.0) ? 1.0 : (vol < 0.0) ? 0.0 : vol; mix->set_volume(number,vol); act("CLI: volume set to %.2f",vol); } break; case 'S': float pos; if(sscanf(optarg,"%f",&pos)==EOF) error("CLI: invalid position for channel %i",number); else { pos = (pos>1.0) ? 1.0 : (pos < 0.0) ? 0.0 : pos; mix->set_position(number,pos); act("CLI: starting from position %.2f",pos); } break; case 'P': playmode = 0; if(strncasecmp(optarg,"play",4)==0) playmode=PLAYMODE_PLAY; if(strncasecmp(optarg,"cont",4)==0) playmode=PLAYMODE_CONT; if(strncasecmp(optarg,"loop",4)==0) playmode=PLAYMODE_LOOP; if(!playmode) error("invalid playmode %s",optarg); else // mix->set_playmode(number,playmode); act("CLI: set playmode \"%s\" for following channels",optarg); break; case 'g': #ifdef GUI_NIGHTOLO if(strcasecmp(optarg,"gtk")==0) thegui=GTK1; #endif #ifdef GUI_NIGHTOLO2 if(strcasecmp(optarg,"gtk2")==0) thegui=GTK2; #endif #ifdef GUI_RUBIK if(strcasecmp(optarg,"ncurses")==0) thegui=NCURSES; #endif if(strcasecmp(optarg,"cli")==0) thegui=CLI; if(thegui>=0) break; notice("listing available user interfaces:"); #ifdef GUI_NIGHTOLO act("[gtk] - Xwin graphical interactive clicky clicky"); #endif #ifdef GUI_NIGHTOLO2 act("[gtk2] - new graphical interactive, experimental with languages"); #endif #ifdef GUI_RUBIK act("[ncurses] - 0ld sch00l l33t console display"); #endif act("[cli] - command line interface, not interactive"); exit(0); case 's': if(!outch) { error("invalid command line argument: server"); error("you must specify a codec first with the -e option"); break; } iceid = outch->create_ice(); if(iceid<0) { error("could'nt create icecast shouter"); break; } else func("created icecast shouter ID %i",iceid); ice = outch->get_ice(iceid); char *p; p = strstr(optarg,":"); if(p) { ice->port( atoi(p+1) ); *p = '\0'; } else ice->port(8000); ice->host(optarg); notice("CLI: created streamer to %s %i", ice->host(), ice->port()); break; case 'l': if(!outch) { error("invalid command line argument: server login type"); error("you must specify a codec first with the -e option"); break; } if(!iceid) { error("invalid command line argument: server login type"); error("you must specify a server first with the -s option"); break; } int ltype; if(strncasecmp(optarg,"ice1",4)==0) ltype = SHOUT_PROTOCOL_XAUDIOCAST; else if(strncasecmp(optarg,"ice2",4)==0) ltype = SHOUT_PROTOCOL_HTTP; else if(strncasecmp(optarg,"icy",4)==0) ltype = SHOUT_PROTOCOL_ICY; else { error("unrecognized login type: %s",optarg); error("please use one of the following:"); error("ice1 = icecast 1, darwin and litestream"); error("ice2 = icecast 2 server"); error("icy = shoutcast server"); break; } ice = outch->get_ice(iceid); ice->login(ltype); act("CLI: login type set to %s",optarg); break; case 'p': if(!outch) { error("invalid command line argument: server password"); error("you must specify a codec first with the -e option"); break; } if(!iceid) { error("invalid command line argument: server password"); error("you must specify a server first with the -s option"); break; } ice = outch->get_ice(iceid); ice->pass(optarg); act("CLI: stream password set"); break; case 'm': if(!outch) { error("invalid command line argument: server mountpoint"); error("you must specify a codec first with the -e option"); break; } if(!iceid) { error("invalid command line argument: server mountpoint"); error("you must specify a server first with the -s option"); break; } ice = outch->get_ice(iceid); ice->mount(optarg); act("CLI: stream mountpoint %s",ice->mount()); break; case 'n': if(!outch) { error("invalid command line argument: stream name"); error("you must specify a codec first with the -e option"); break; } if(!iceid) { error("invalid command line argument: stream name"); error("you must specify a server first with the -s option"); break; } ice = outch->get_ice(iceid); ice->name(optarg); act("CLI: stream descriptive name: %s",ice->name()); break; case 'u': if(!outch) { error("CLI: invalid command line argument: stream url"); error("you must specify a codec first with the -e option"); break; } if(!iceid) { error("CLI: invalid command line argument: stream url"); error("you must specify a server first with the -s option"); break; } ice = outch->get_ice(iceid); ice->url(optarg); act("CLI: stream descriptive: url %s",ice->url()); break; case 'd': if(!outch) { error("CLI: invalid command line argument: stream description"); error("you must specify a codec first with the -e option"); break; } if(!iceid) { error("CLI: invalid command line argument: stream description"); error("you must specify a server first with the -s option"); break; } ice = outch->get_ice(iceid); ice->desc(optarg); act("CLI: stream description: %s",ice->desc()); break; case 1: act("CLI: queue %s on channel %i",optarg,number); if(!mix->chan[number]) if(!mix->create_channel(number)) { error("CLI: can't create channel %i",number); break; } else { notice("CLI: created channel %i",number); mix->set_playmode(number,playmode); } if(!mix->add_to_playlist(number,optarg)) error("CLI: can't add %s to channel %1",optarg,number); break; default: break; } } while(res > 0); return true; } bool check_config() { /* checking config directory TODO: FIXME PLEASE */ char temp[MAX_PATH_SIZE]; char *home = getenv("HOME"); sprintf(temp,"%s/.muse",home); mkdir(temp,0744); return(true); } int main(int argc, char **argv) { notice(version,PACKAGE,VERSION); act("by Denis \"jaromil\" Rojo http://rastasoft.org"); act("--"); /* register signal traps */ if (signal (SIGINT, quitproc) == SIG_ERR) { error ("Couldn't install SIGINT handler"); exit (0); } if (signal (SIGQUIT, quitproc) == SIG_ERR) { error ("Couldn't install SIGQUIT handler"); exit (0); } if (signal (SIGTERM, quitproc) == SIG_ERR) { error ("Couldn't install SIGTERM handler"); exit (0); } got_sigpipe = false; if (signal (SIGPIPE, fsigpipe) == SIG_ERR) { error ("Couldn't install SIGPIPE handler"); exit (0); } mix = new Stream_mixer(); #ifdef HAVE_SCHEDULER rscheduler = new Scheduler_xml( sched_file_path() ); mix->register_sched( rscheduler ); rscheduler->register_mixer(mix); rscheduler->lock(); rscheduler->start(); rscheduler->wait(); rscheduler->unlock(); //rscheduler->play(); #endif if( !take_args(argc, argv) ) goto QUIT; check_config(); // setup soundcard mix->set_live(micrec); mix->set_lineout(dspout); // if(!snddev) { // warning("no soundcard found"); // act("line-in and speaker out deactivated"); // } if(thegui==GTK1 || thegui==GTK2) if(!getenv("DISPLAY")) { /* no graphical environment */ error("DISPLAY not found, falling back to console"); thegui=-1; } if(thegui<0) { /* select default gui */ if(getenv("DISPLAY")) { /* we are in a graphical environment */ #ifdef GUI_NIGHTOLO thegui=GTK1; #elif GUI_NIGHTOLO2 thegui=GTK2; #endif } if(thegui<0) { /* if GUI is still not selected */ #ifdef GUI_RUBIK thegui = NCURSES; #else thegui=CLI; #endif } } switch(thegui) { case GTK1: #ifdef GUI_NIGHTOLO notice("spawning the GTK-1.2 GUI"); act("by nightolo "); warning("GTK-1.2 should be upgraded to GTK 2 on your system"); warning("this old version of the interface is much unstable"); gui = new GTK_GUI(argc,argv,mix); #else error("the Gtk-1.2 interface is not compiled in"); #endif break; case GTK2: #ifdef GUI_NIGHTOLO2 notice("spawning the GTK-2 GUI"); act("by Antonino \"nightolo\" Radici "); gui = new GTK2_GUI(argc,argv,mix); #else error("the Gtk2 interface is not compiled in"); #endif break; case NCURSES: #ifdef GUI_RUBIK notice("spawning the NCURSES console user interface"); act("by Luca \"rubik\" Profico "); gui = new NCURSES_GUI(argc,argv,mix); MuseSetLog("muse.log"); #else error("the ncurses console interface is not compiled in"); #endif break; default: notice("using commandline interface (non interactive)"); thegui=CLI; } if(thegui<0) error("no interface selected, this should never happen"); if(thegui!=CLI) { gui->start(); mix->register_gui(gui); set_guimsg(gui); notice("%s version %s",PACKAGE,VERSION); } /* apply configuration and startup all registered encoders */ outch = (OutChannel*)mix->outchans.begin(); while(outch) { if( (outch->icelist.len() == 0) && !outch->fd) { /* check if its worth to encode */ error("codec %s [%u] has no server neither filedump configured", outch->name,outch->id); mix->delete_enc(outch->id); error("encoder removed"); } else if(mix->apply_enc( outch->id )) outch->start(); if(!outch) break; outch = (OutChannel*) outch->next; } if(thegui==CLI) { /* CLI interface logics ======================= */ int c; for(c=0;cchan[c]) if(!mix->set_channel(c,1)) error("CLI: error in set_channel(%i,1)",c); else if(!mix->play_channel(c)) error("CLI: error in play_channel(%i)",c); else has_playlist = true; } /* === END CLI === */ if((!has_playlist && !micrec) && (thegui==CLI)) { warning("nothing to play, you must specify at least songs or live input"); act("see --help switch for more information"); goto QUIT; } /* MAIN LOOP */ while(!mix->quit) mix->cafudda(); /* simple isn't it? */ QUIT: notice("quitting MuSE"); if(thegui!=CLI) gui->quit = true; set_guimsg(NULL); #ifdef HAVE_SCHEDULER rscheduler->stop(); rscheduler->quit = true; jsleep(0,50); delete rscheduler; rscheduler = NULL; #endif if(mix) { act("stopping mixer..."); /* piglia o'tiemp e sputa in terra senza fa' troppo casino a segnala' ai canali */ jsleep(0,50); delete mix; } if(thegui!=CLI) { act("quitting graphic interface"); delete gui; } act("cya on http://muse.dyne.org"); MuseCloseLog(); exit(0); } /* signal handling */ void quitproc (int Sig) { func("received signal %u on process %u",Sig,getpid()); if(thegui!=CLI) gui->quit = true; mix->quit = true; } void fsigpipe (int Sig) { warning("received signal SIGPIPE (%u) on process %u",Sig,getpid()); got_sigpipe = true; }