Web Stream Information
The web stream(s) is a downmixed version of the broadcast stream that is created in the on-air studio. The original stream is decoded, then re-encoded at a different quality and presented to the listening public. The software used at every stage of this process is Open Source Software (1).
Decoding the original stream
The server at valleyfreeradio.org listens to the original stream using the command `/usr/bin/ogg123 http://IP.OF.STUDIO/test.ogg -q -d raw -f -` (2) which translates more or less to “play this audio stream without header info, and dump the output to the screen instead of the soundcard.” The reason for the printing the raw audio data to the screen, or STDOUT as it is known, is so it can be in turn read by another program.
Encoding the new streams
The program reading the raw audio from the command line is called ices (3). The function of ices is to present audio wrapped in a package understandable by the icecast server (4). Ices is controlled through the xml configuration file listed here:
<?xml version="1.0"?>
<ices>
<!– run in background –>
<background>0</background>
<!– where logs go. –>
<logpath>/var/lib/ices</logpath>
<logfile>ices.log</logfile>
<!– size in kilobytes –>
<logsize>2048</logsize>
<!– 1=error, 2=warn, 3=infoa ,4=debug –>
<loglevel>4</loglevel>
<!– logfile is ignored if this is set to 1 –>
<consolelog>0</consolelog>
<stream>
<!– metadata used for stream listing –>
<metadata>
<name>Valley Free Radio</name>
<genre>Free Speech</genre>
<description>103.3 FM WXOJ-LP Northampton MA</description>
<url>http://www.valleyfreeradio.org</url>
</metadata>
<!– Input module.
This example uses the ‘oss’ module. It takes input from the
OSS audio device (e.g. line-in), and processes it for live encoding. –>
<!–input>
<module>stdinpcm</module>
<param name=”rate”>44100</param>
<param name=”channels”>2</param>
<param name=”metadata”>1</param>
<!– <param name=”metadatafilename”>/usr/local/share/ices/metadata</param>–>
</input>
< <instance>
<hostname>66.249.31.180</hostname>
<port>8000</port>
<password>PASSWORD</password>
<mount>/high.ogg</mount>
<reconnectdelay>8</reconnectdelay>
<reconnectattempts>-1</reconnectattempts>
<maxquelength>80</maxquelength>
<yp>1</yp> allow stream to be advertised on YP, default 0 –>
<!– Live encoding/reencoding:
channels and samplerate currently MUST match the channels
and samplerate given in the parameters to the oss input
module above or the remsaple/downmix section below. –>
<!– <encode>
<quality>4</quality>
<samplerate>44100</samplerate>
<channels>2</channels>
</encode>
</instance> –>
<instance>
<hostname>66.249.31.180</hostname>
<port>8001</port>
<password>PASSWORD</password>
<mount>/mid.ogg</mount>
<reconnectdelay>8</reconnectdelay>
<reconnectattempts>-1</reconnectattempts>
<!– stereo->mono downmixing, enabled by setting this to 1–>
<!– <downmix>1</downmix>–>
<encode>
<quality>6</quality>
<samplerate>22050</samplerate>
<channels>1</channels>
</encode>
<downmix>1</downmix>
<resample>
<in-rate>44100</in-rate>
<out-rate>22050</out-rate>
</resample>
</instance>
<instance>
<hostname>66.249.31.180</hostname>
<port>8002</port>
<password>PASSWORD</password>
<mount>/low.ogg</mount>
<reconnectdelay>8</reconnectdelay>
<reconnectattempts>-1</reconnectattempts>
<encode>
<quality>2</quality>
<samplerate>22050</samplerate>
<channels>1</channels>
</encode>
<downmix>1</downmix>
<resample>
<in-rate>44100</in-rate>
<out-rate>22050</out-rate>
</resample>
</instance>
</stream>
</ices>
(note: the passwords aren’t really PASSWORD.)
Ices is configured to read raw audio from stdin, through a pipe. A pipe looks like this: ” | ” and is Linux/Unix/BSD for “take the output from the command before this and use it as the input for the command after this”. Because of the pipe, ices needs to executed in the same line as the ogg123 command from above.
It is probably hard to notice here, but the first stream instance in this configuration is commented out. In an editing session this would be very clear due to syntax highlighting. The commenting out was done because of bandwidth and cpu concerns, and will (hopefully) change in the future. To re-enable the ‘high.ogg’ just erase the “<!–” at the beginning and the “–>″ at the end of the section, and then type `killall ices icecast ogg123` a few times in the terminal, until it says “no process killed” for each of them. When a minute passes, a script will notice that the stream is dead and restart it, bringing the high.ogg back with it. That script will be discussed later on.
Making the streams accessible
The icecast server is what provides the links to the streams that we click on. Icecast server cannot be run as a normal user, so there is an ‘icecast’ user on the VPS. If there are streams present to serve up, icecast does so according to this xml configuration file:
<icecast>
<limits>
<sources>10</sources>
<clients>100</clients>
<threadpool>5</threadpool>
<queue-size>102400</queue-size>
<client-timeout>30</client-timeout>
<source-timeout>30</source-timeout>
<burst-on-connect>1</burst-on-connect>
<burst-size>65536</burst-size>
</limits>
<authentication>
<source-password>PASSWORD</source-password>
<relay-password>hackme</relay-password>
<admin-user>admin</admin-user>
<admin-password>ANOTHERPASSWORD</admin-password>
</authentication>
<hostname>66.249.31.180</hostname>
<listen-socket>
<port>8000</port>
</listen-socket>
<mount>
<mount-name>/high.ogg</mount-name>
<max-listeners>10</max-listeners>
</mount>
<listen-socket>
<port>8001</port>
</listen-socket>
<mount>
<mount-name>/mid.ogg</mount-name>
<max-listeners>10</max-listeners>
</mount>
<listen-socket>
<port>8002</port>
</listen-socket>
<mount>
<mount-name>/low.ogg</mount-name>
<max-listeners>15</max-listeners>
/> </mount>
<listen-socket>
<port>8003</port>
</listen-socket>
<mount>
<mount-name>/wxoj.mp3</mount-name>
<max-listeners>15</max-listeners>
</mount>
<fileserve>1</fileserve>
<paths>
<logdir>/var/lib/icecast</logdir>
<webroot>/usr/local/share/icecast/web</webroot>
<adminroot>/usr/local/share/icecast/admin</adminroot>
<alias source=”/” dest=”/status.xsl”/>
</paths>
<logging>
<accesslog>access.log</accesslog>
<errorlog>error.log</errorlog>
<loglevel>4</loglevel> <!– 4 Debug, 3 Info, 2 Warn, 1 Error –>
</logging>
</icecast>
Well then, if all is going according to plan you will see the icecast server here.
Some xml things to notice: there is currently a configuration for “high.ogg” and “wxoj.mp3″ that are going unused. High.ogg was explained above, but wxoj.mp3 has been a major source of server slowdown in the past and as such was scrapped pending discovery of an efficient way to re-encode ogg to mp3 and serve it up. THIS ISSUE NEEDS ATTENTION. Politically favored or not, many people find ogg vorbis too inconvenient and want mp3.
The <source-password> is the password that ices uses to tell icecast it’s legit. The port numbers are arbitrary, but traditionally audio is served up around 8000. <mount-name> and <max-listeners> will probably be the only things you’d want to change here, short of adding more streams.
Keeping it all running
This stuff crashes a lot. In order to restart it all, two cron (5) scheduled Perl scripts run once every minute except for minute 59. The first one is /sbin/stream_watcher.pl and it goes a little something like:
#### stream_watcher.pl
my @proclist = ();
my $oggrunning = 0;
my $icesrunning = 0;
my $ezrunning = 0;
open BS,”ps -ef|” or die “can’t open the BS:$!\n”;
while (<BS>)
{
my @fields = split /\s+/;
push @proclist, $fields[7];
}
foreach (@proclist)
{
if (/\/usr\/local\/bin\/ogg123*$/)
{
$oggrunning=1;
}
if (/\/usr\/local\/bin\/ices*$/)
{
$icesrunning= 1;
}
if (/\/usr\/local\/bin\/ezstream*$/)
{
$ezrunning=1;
}
}
# we’ve checked to see if the stream looks ok. if not,
# we re-run the startup script
if (($icesrunning == 0) || ($oggrunning == 0))
{
exec ‘/etc/rc.local’;
}
#if ($ezrunning == 0)
#{
# exec ‘/etc/rc.d/rc.mp3_0′;
#}
This script checks the output of the `ps` command for running instances of ices, ogg123, and ezstream. Ezstream was the ices-like program that was previously used to serve up an mp3 stream, but then nixed due to excessive processor usage. Any how, this stream executes `/etc/rc.local` if either no ogg123 or ices are found. The contents of rc.local are just:
/usr/local/bin/ogg123 http://IP.OF.STUDIO/test.ogg -q -d raw -f - | /usr/local/bin/ices /usr/local/share/ices/Aces-tentative.xml &
…which should make sense by now. The only new thing here is the ampersand, which forces a command to disappear into the background while running.
The other cron script is /sbin/icecast_watcher.pl and it does the same stuff as /sbin/stream_watcher.pl, except it does it for icecast. It looks like this:
#### icecast_watcher.pl
my @proclist = ();
my $running = 0;
open BS,”ps -ef|” or die “can’t open the BS:$!\n”;
while (<BS>)
{
my @fields = split /\s+/;
push @proclist, $fields[7];
}
foreach (@proclist)
{
if (/\/usr\/local\/bin\/icecast*$/)
{
$running = 1;
}
}
# we’ve checked to see if icecast is still coming in. if not,
# we re-run the startup script
if ($running == 0)
{
print ” whoops…\nrestarting the icecast server.\n”;
system(”su icecast -c /usr/local/share/icecast/doc/hello &”);
}
The system call right there might look a little strange. What it does is switch to the user “icecast” temporarily and run the command `/usr/local/share/icecast/doc/hello` in the background. the “hello” script is just an alias for
/usr/local/bin/icecast -c /usr/local/share/icecast/doc/icecast-min.xml
For whatever reason, ices can sometimes cease to provide a stream but not die. Other times the stream will degrade into an annoying clicking square wave kind of noise. This unfortunate situation seems to arise after a few hours of otherwise good behavior, and is no good for us. To deal with it. a third and final script is croned to run every 3 hours. The entire function of this script is to kill all of this junk, which in turn forces the other scripts to restart ices and ogg123 and icecast. It is called /sbin/pid_slap.pl and it goes:
### pid_slap.pl
# this perl script finds all the ogg123's and ices' and kills them dead. they make a nasty clicking noise if
# left alone too long.
my @oggs = split /\n/, `ps -axw|grep ogg123`;
my @icess = split /\n/, `ps -axw|grep ices`;
my @dummy = split /\//, $0;
my $SCRIPT_SHORT = $dummy[$#dummy];
my $oggsrunnin = 0;
my $icessrunnin =0;
my $PID = 0;
my @PIDs;
foreach (@oggs) {
next if $_ =~ /grep ogg123/ or $_ =~ /$SCRIPT_SHORT $ARGV[0]/;
$oggsrunnin++;
$PID = $_;
$PID += 0;
push (@PIDs, $PID);
}
foreach (@icess) {
next if $_ =~ /grep ices/ or $_ =~ /$SCRIPT_SHORT $ARGV[0]/;
$icessrunnin++;
$PID = $_;
$PID += 0;
push (@PIDs, $PID);
}
print “found $oggsrunnin oggs runnin ‘n $icessrunnin icess runnin…\n”;
foreach (@PIDs) {
$killems = sprintf (”$killems %i”, $_);
}
print “killin ‘em\n”;
print `kill -9 $killems`;
What is happening here is a list is built containing the system process identification numbers, or PIDs, of the ogg123’s and ices’ that are running, and then the list is sent to the `kill` command. Blunt yet effective.
References
(1). http://www.opensource.org/
(2). http://linuxcommand.org/man_pages/ogg1231.html
(3). http://www.icecast.org/ices.php
(5). cron & crontab










