|
From: D. S. <new...@gm...> - 2008-02-06 02:51:27
|
icecast, simple sox soundcard streaming server Sometimes you want to share what you're hearing with others. One way to do that is with a streaming audio server. Others can then listen with clients like XMMS, WinAmp, or Windows Media Player. sox + perl + tcpserver (and maybe lame) If you can make sox open your soundcard (like with ossdsp, etc.), and you have tcpserver (and perl), then you can easily use sox (and, optionally, lame) to act as a icecast audio streaming server. The beauty of using sox is ... sox. I like the compand effect for processing volume levels for voice at lower bitrates. Whatever your soundcard hears, you can serve up to others, using this approach. One nice thing about sox in the pipeline is that you can convert to different formats. You can at the same time serve up various mp3 formats, ogg formats. Tcpserver can be used to limit the number of simultaneous connections. =[ icystream.pl ]========================= #!/usr/bin/perl $Usage = <<"END"; Lightweight, simple mp3 streaming using icecast protocol Usage: icystream.pl [options] [rate] for mp3, rate is a lame -b bitrate arg. For other audio formats, rate is sox -r rate arg. Example: tcpserver -v -H -l 0 -R -D -c 4 \$IP \$PORT perl icystream.pl 16 Options: -i IP, --ip IP ip, or base/host url -p P, --port P port -u, --urls list urls clients may use to listen (use ip, port) -ht, --html list urls ", as html -h, --help this usage help Adapted from: http://search.cpan.org/src/ALLENDAY/MP3-Icecast-0.02/Icecast.pm http://www.blott-online.com/soundcard.html http://sphere.sourceforge.net/flik/docs/streaming.html , etc. END my $argc = 0; my ($List_Urls,$Html_List_Urls); my ($IP,$Port); while ( $ARGV[ $argc ] =~ /^[\-\/]{1,2}(.*)$/ ) { local($flag) = $1; $argc++; # process -{arg} if ( ($flag =~ /^help$/i) || ($flag =~ /^h$/) ) { print STDERR $Usage; exit; } elsif ( ($flag =~ /^urls$/i) || ($flag =~ /^u$/) ) { $List_Urls = 1; } elsif ( ($flag =~ /^html$/i) || ($flag =~ /^ht$/) ) { $Html_List_Urls = 1; } elsif ( ($flag =~ /^ip$/i) || ($flag =~ /^i$/) ) { $IP = $ARGV[$argc++]; } elsif ( ($flag =~ /^port$/i) || ($flag =~ /^p$/) ) { $Port = $ARGV[$argc++]; } } # bit rate or sample rate ... my $Rate = $ARGV[$argc] || 8; # nb: the config file can override this! my $IceInfoConfigFile = "iceinfo.cfg"; # optional my %Config; $Config{'ServerActivityTimeout'} = 45; # secs $Config{'description'} = 'unknown'; $Config{'genre'} = 'unknown genre'; $Config{'path'} = ''; $Config{'buffersize'} = 8 * 2**10; my @Mp3_Rates = (8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320); my @Ogg_Rates = (32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 500); my @Wav_Rates = (8000, 11025, 22050, 44100); my $MimeType = { 'mp3' => 'audio/mpeg', # or 'audio/x-mp3' 'ogg' => 'application/ogg', 'wav' => 'audio/x-wav', # 'au' => 'audio/au', # winamp won't stream this one, just d/l }; my $Ext2SoxType = { 'mp3' => 'mp3', 'ogg' => 'ogg', 'wav' => 'raw', # ? # 'au' => 'au', }; my $CRLF = "\015\012"; # read_config if ($IceInfoConfigFile && open(CFG,"<$IceInfoConfigFile")) { while(my $l = <CFG>) { if (($l !~ /^#/) && ($l =~ /^([^:]+):(.*)[\s\r\n]*$/)) { my ($a,$v) = ($1,$2); $v =~ s/[\n\r\s]*$//gs; $Config{$a} = $v; } } close CFG; } if ($Config{'rate'}>0) { $Rate = $Config{'rate'} } # list_urls if ($List_Urls>0) { print "http://$IP:$Port ($Rate kbps mp3)\n"; foreach $br (@Mp3_Rates) { print "http://$IP:$Port/${br}kbps/stream.mp3\n" if ($br <= $Rate); } foreach $br (@Ogg_Rates) { print "http://$IP:$Port/${br}kbps/stream.ogg\n" if ($br <= $Rate); } # foreach $br (@Wav_Rates) { # print "http://$IP:$Port/${br}hz/stream.wav\n" # if ($br <= ($Rate*1000)); # } exit; } # html_list_urls if ($Html_List_Urls>0) { print "<OL><B>Available Streams</B>\n"; my $u = "http://$IP:$Port"; print "<LI><A HREF='$u'>$u</A> - $Rate kbps mp3 (default)</LI>\n"; print "<P/>\n"; foreach $br (@Mp3_Rates) { if ($br <= $Rate) { my $u = "http://$IP:$Port/${br}kbps/stream.mp3"; print "<LI><A HREF='$u'>$u</A> - $br kbps mp3</LI>\n"; } } print "<P/>\n"; foreach $br (@Ogg_Rates) { if ($br <= $Rate) { my $u = "http://$IP:$Port/${br}kbps/stream.ogg"; print "<LI><A HREF='$u'>$u</A> - $br kbps ogg</LI>\n"; } } print "<P/>\n"; # foreach $br (@Wav_Rates) { # if ($br <= ($Rate*1000)) { # my $u = "http://$IP:$Port/${br}hz/stream.wav"; # print "<LI><A HREF='$u'>$u</A> - $br bps wav</LI>\n"; # } # } print "</OL>\n"; exit; } # make audio stream (mp3) output to stdout my ($method,$url,$protocol); my $meth_url_proto = <STDIN>; # get http method url protocol ($method,$url,$protocol)=($1,$2,$3) if ($meth_url_proto =~ /^(.*) (.*) (.*)$/); my $ext = ($url =~ /\.([^\.]+)[\n\r]*$/ ? $1 : 'mp3'); # [mp3 if can't tell from url] my $url_rate = ($url =~ /rate(\d+)\D/) && $1 || ($url =~ /\D(\d+)k?bps/) && $1 || ($url =~ /\D(\d+)hz/i) && $1 || $Rate; # use url kbps if same or lower: my $br = ($url_rate <= $Rate ? $url_rate : $Rate); my $offset = 0; my $size= 0; my $header = ''; my $mime = $MimeType->{lc($ext)}; print STDERR "serve: pid:$$ method:$method url:$url"; print STDERR " protocol:$proto ext:$ext mime:$mime rate:$br\n"; $header .= "ICY ". ($offset ? 206 : 200) ." OK$CRLF"; $header .= "icy-notice1:<BR>"; $header .= "This stream requires a shoutcast/icecast compatible player.<BR>$CRLF"; $header .= "icy-notice2:lwic<BR>$CRLF"; $header .= "icy-name:$Config{'description'}$CRLF"; $header .= "icy-genre:$Config{'genre'}$CRLF"; $header .= "icy-url: $Config{'path'}$CRLF"; $header .= "icy-pub:1$CRLF"; $header .= "icy-br:$br$CRLF"; $header .= "Content-Type: $mime$CRLF"; $header .= "$CRLF"; binmode(STDOUT); # ok ... icy header is ready, but hold off sending it till # the stream is ready, too. # prepare command to stream audio my $cmd; my $fx = $Config{'sox-effects'}; my $in_t = (defined $Config{'sox-in-type'} ? $Config{'sox-in-type'} : ' -t ossdsp '); my $in_dev = (defined $Config{'sox-in-file'} ? $Config{'sox-in-file'} : '/dev/dsp'); if ($ext ne 'mp3') { # non mp3 - sox alone ...: $br = 8000 if ($br<8000); # specified a lame bitrate for non mp3 ... use low br wav my $chanarg = ($Config{'channels'}>0 ? '-c '.$Config{'channels'} : ''); $cmd = "sox $in_t -r $br $chanarg $in_dev -t " .$Ext2SoxType->{lc($ext)}." -r $br $chanarg - $fx"; # just sox } else { # mp3 ... if ($br <= 16 ) { # lower mp3 bitrates - sox alone $cmd = "sox $in_t $in_dev -t mp3 -r ".($br*1000)." - $fx"; } else { # higher mp3 bitrates - sox+lame my $chanarg = ($Config{'channels'}>0 ? '-c '.$Config{'channels'} : '-c 2'); my $soxraw = (defined $Config{'sox-raw'} ? $Config{'sox-raw'} : "-t raw -w -r 44100 -s -c 2 "); my $lame_in_args = (defined $Config{'lame-in-args'} ? $Config{'lame-in-args'} : '--silent -r -x'); my $sox_high_inargs = (defined $Config{'sox-highspeed-in-args'} ? $Config{'sox-highspeed-in-args'} : '-w -r 44100'); $cmd = "sox $in_t $sox_high_inargs $chanarg $in_dev $soxraw - $fx" ." | lame $lame_in_args -b $br - -"; } } print STDERR "icystream: $$ : $cmd\n"; # stream it! open(STREAM, "$cmd |") || die "can't open sox stream for: $cmd"; binmode(STREAM); eval { # local $SIG{ALRM} = sub { die "alarm\n"; }; # sub won't perlcc compile ... # alarm $Config{'ServerActivityTimeout'}; # secs # local $SIG{PIPE} = sub { die "icystream: $$ : broken pipe for: $cmd"; }; while ( fileno(STREAM) && fileno(STDOUT) && read(STREAM, $buff, $Config{'buffersize'})) { if ($header) {print STDOUT $header; $header = undef;} print STDOUT $buff; # alarm $Config{'ServerActivityTimeout'}; # secs # the way a disconnect is usually detected: if ($! =~ /Connection/i) { # eg, "Connection reset by peer" etc. print STDERR "icystream: $$ : $! for: $cmd\n"; exit 0; } elsif ($!) { print STDERR "icystream: $$ : $!\n"; } } }; if ($@) { print STDERR $@,"\n"; exit 0 unless $@ eq "alarm\n"; # unexpected errors # it was a timeout ... print STDERR "icystream: $$ : client inactivity time out for: $cmd\n"; exit 0; } ============================= And here's a sample (optonal) iceinfo.cfg configuration file: ==[ iceinfo.cfg ]================== # *** LightWeight IceCast Configurable Settings *** # ... all optional # short text: description:LWIC - lightweight icecast # mp3 id genre: genre:Other # sent back to client as icy-url: path: # any sox effect(s) (see sox man, # http://en.wikipedia.org/wiki/Audio_level_compression, etc.) sox-effects:compand 0.1,0.3 -60,-60,-30,-15,-20,-12,-4,-8,-2,-7 # (You probably want to comment this line out if you're acting # as a relay server, otherwise you will re-compress the signal.) # channels, 1 or 2 (where there's a choice): channels:2 # The (maximum/default) bit/sample rate. # If defined here, overrides everything else: # Lame-style bitrates allowed: # 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, # 144, 160,192, 224, 256, 320 # Clients can specify other (lower) rates in the url, like # http://site.net:8080/16kbps/stream.mp3 # http://site.net:8080/32kbps/stream.ogg etc. #rate:64 # buffer size in bytes #buffersize:8192 # more settings, their defaults - # #sox-in-type:-t ossdsp #sox-in-file:/dev/dsp #sox-highspeed-in-args:-w -r 44100 #sox-raw:-t raw -w -r 44100 -s -c 2 #lame-in-args:--silent -r -x ========================== You should be able to test if you have your mic turned on and audio mixer set properly with a simple test like this: ./icystream.pl > t.mp3 Hit enter twice, and say something into the mic (or otherwise have something playing), Ctrl-c to stop the recording. play t.mp3 Should then play back the captured sound. serving We need to know an IP the outside world can use to reach our server. If you don't tell it what speed, it defaults to 8kbps. The -u infomation option tells icystream.pl to just list the urls clients may use to reach us, and exit. $ ./icystream.pl -i 11.22.33.44 -p 8080 -u http://11.22.33.44:8080 (8 kbps mp3) http://11.22.33.44:8080/8kbps/stream.mp3 Or if we're want things at a higher bitrate, we get more choices. $ ./icystream.pl -i 11.22.33.44 -p 8080 -u 128 http://11.22.33.44:8080 (128 kbps mp3) http://11.22.33.44:8080/8kbps/stream.mp3 http://11.22.33.44:8080/16kbps/stream.mp3 http://11.22.33.44:8080/24kbps/stream.mp3 http://11.22.33.44:8080/32kbps/stream.mp3 http://11.22.33.44:8080/40kbps/stream.mp3 http://11.22.33.44:8080/48kbps/stream.mp3 http://11.22.33.44:8080/56kbps/stream.mp3 http://11.22.33.44:8080/64kbps/stream.mp3 http://11.22.33.44:8080/80kbps/stream.mp3 http://11.22.33.44:8080/96kbps/stream.mp3 http://11.22.33.44:8080/112kbps/stream.mp3 http://11.22.33.44:8080/128kbps/stream.mp3 http://11.22.33.44:8080/32kbps/stream.ogg http://11.22.33.44:8080/48kbps/stream.ogg http://11.22.33.44:8080/64kbps/stream.ogg http://11.22.33.44:8080/80kbps/stream.ogg http://11.22.33.44:8080/96kbps/stream.ogg http://11.22.33.44:8080/112kbps/stream.ogg http://11.22.33.44:8080/128kbps/stream.ogg Finally, we start up the server and allow the outside world to connect. In this example our server's at IP 11.22.33.44 and listening on port 8080, and we only allow 4 connections at once: tcpserver -v -H -l 0 -R -D -c 4 11.22.33.44 8080 perl icystream.pl 128 Ok, maybe the perl hack above could be simpler and prettier. |