Barnyard BBS
I say, let me never be complete.
I say, let me never be content.
Audio Streaming Notes, Example, and Source Code
Overview
I've spent quite some time working with streaming radio lately, and I thought
it might be useful to share some of my experiences.
In case you don't know about my background, I am a volunteer with
WMPH 91.7 here in Wilmington. I maintain their website and streaming systems.
WMPH Technology
Our station runs internally on a software package called WaveStation, by
BSI. WaveStation has been
"replaced" by a nearly identical product known as Simian. This
software controls all the content of the station. This includes all
the music, public service announcements, and voice talent. WaveStation
produces most of what goes to both our terrestrial transmitter and our
internet broadcaster.
We use SimpleCast as our
encoder. This is where we convert the audio signal produced at our
studio into an MP3 stream that is destined for our internet broadcast.
Currently, we encode three versions simultaneously (three levels of quality)
We use Icecast as our internet
broadcasting software. This runs on our webserver, and does the "heavy
lifting" of the internet transmission.
Also, we use some custom ASP.Net code for the On Demand stuff.
More detail on that later.
Technology Rationale
- Why WaveStation? It's what we have. It works well, and it
would be expensive to replace. Not to mention, we have a lot of
automation based on it.
- SimpleCast is a great
product. We use it because it makes it very easy to encode several
streams simultaneously. We use it both at the station, and for live
remote broadcasts. Cool little program.
- We originally used Shoutcast. There's nothing wrong with
Shoutcast. It's a good product. We switched to Icecast
for a few reasons. Firstly, I
like open source. Secondly, Shoutcast is dead. I don't know of
any further development on it. That worries me. Lastly, Icecast offers some advantages over Shoutcast.
- Icecast can host multiple "mountpoints" on a single port. A
"mountpoint" is a stream of a particular bitrate and content. This
is very helpful.
- Icecast can also perform authentication in several ways, such as
htpasswd and via scripting.
- Icecast installs as a service. This is just plain helpful.
I know you can run Shoutcast with SrvAny. I did it. I like
this better.
- I use some ASP.Net code to supplement Icecast's streaming abilities.
Icecast is great; but for a "file-served" operation, like On Demand
file streaming, it just wasn't right. I wrote the code because I wanted
more security than just hosting the files in open web space.
Skip to the code.
Points of Interest
- Total Control Radio (WaveStation Database Automation)
- WaveStation uses standard Access MDB databases for a lot of its
work. This is especially useful because it allows you to modify
the WaveStation databases very easily through code. This is how we
make the
Total
Control Radio program operate. Total Control Radio is actually
powered by two parts:
- The first part is the website. The website has its own
database and controls all the voting and decision-making. This
is just a simple ASP.Net setup. The website also provides a
XML web-service. You'll see why that's important in a minute.
- The second part is a script that runs internally at the station.
Total Control Radio is just a standard BSI log template. It starts
the same way each week. The script is executed once for each
song that is played. It uses the web-service to determine what
should be played next, and makes appropriate changes to the
WaveStation program log.
- Words of caution: You will likely be tempted to make
application (executable) calls from within a program log. Do not attempt to
make changes to the same program log from an application call.
The changes will not be recognized. Use an external way
to run your script, such as Windows Task Scheduler.
- More words of caution: Once something loads onto one of the virtual "decks", it will not unload if the log changes. That means you need to keep your "decks" full. My log uses lots of REM's for padding. In addition, the show Total Control Radio works about two songs ahead at all times.
- Icecast
- Windows Media Player is just a pain-in-the-ass. I constantly
find myself making accommodations for it.
- WMP has some odd buffering logic. This makes it barf
whenever it is the first to connect using an Icecast on-demand
relay. I'd love to just not support WMP, but the vast majority
of our listeners use it. For this reason, I cannot use on
demand relays in Icecast. This is not the fault of Icecast in
any way, but they are still unusable.
- WMP will barf on variable bitrate streams. Don't use them.
If you stream files with my ASP.Net code, make sure you are
streaming static bitrate.
- WMP does not have support for title metadata (unless you are
using WMA streams). If your WMP clients don't see anything
besides the station information; nothing is wrong. This is
normal. Although I have no proof, I surmise that this was just
to make mp3 streams "less attractive" compared to WMA streams.
- If WMP receives a "content-length" HTTP header, if will enable
the "Save" option to the users. If you are writing your own
streaming code, remember this.
- Icecast has really improved in stability since the early versions.
- If you have interest in writing code to generate Icecast htpasswd
files, they just use a standard MD5 hash.
On Demand Streaming Code
I'm frequently asked how to stream things On Demand using Icecast. This is a common question, so I thought that I'd write up a good answer for everybody.
Icecast is a phenomenal program for broadcasting your content; especially when that content is a continuous stream (like a radio station's broadcast). However, it's not so good for On Demand type broadcasts (where you want each person to start at "the beginning"). Icecast does have the option to serve files via HTTP, but that's just a download.
I've written two versions of my streaming scripts. The first (and primary) is written in ASP.Net. Secondarily, I've got one written in PHP. PHP isn't my main language, and I built it at the request of a friend.
General note on On Demand streaming: You're streaming directly from files in these examples. The way that the files are encoded matters. I've seen cases where a bad encoder didn't include a length header in the file; and things just didn't work right. When in doubt: try a different file in the streamer.
ASP.Net Streaming Code
Here some sample ASP.Net code that you can use to stream mp3's for On Demand use. I use something very similar to stream mp3's and shows
for
WMPH.
I'd like to mention that Kevin Pisarsky has some really helpful
code for reading (and writing) ID3 tags in ASP.Net. I use his code for the
On Demand section (singles) of WMPH. You can use his library to make your scripts more dynamic (where the stream's tags are derived from the files). I didn't include that in the example, because I wanted to the code to be simple and easy to read.
Here are two working demos of the streaming script in action. Both streams are mp3, just with different playlist files (as different players like different formats):
The sample track is "Always Believe" by Sapphirecut. She's a good friend of mine. You can buy her stuff at CDBaby and iTunes.
Download a working sample here (zip includes both the streamer and a sample mp3).
Sample Code
<%@ WebHandler Language="VB" Class="Streamer" %>
'Streaming MP3 Sample
'Ben Yanis, http://www.barnyardbbs.com
'Creative Commons Attribution 2.5
'http://creativecommons.org/licenses/by/2.5/
Imports System
Imports System.Web
Public Class Streamer : Implements IHttpHandler
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
'You could easily make this dymanic
'For example, you could pass parameters in the the querystring
Dim FileName As String = context.Server.MapPath("StreamSample.mp3")
'Set the content type, we're gonna send mp3 data
context.Response.ContentType = "audio/mpeg"
'Name the stream
context.Response.AppendHeader("icy-name", "Sapphirecut - Always Believe (BarnyardBBS Streaming Demo)")
'Give your url
context.Response.AppendHeader("icy-url", "Sapphirecut - Always Believe (BarnyardBBS Streaming Demo)")
'Note: I often read the ID3's from the file directly. I use some code written by Kevin Pisarsky (www.pisarsky.com)
'I left it out of this version for simplicity.
'At this point, you might wonder why we don't just use the WriteFile or TransmitFile method...
'These will *work*, however, they also will send the Content-Length HTTP header. This makes it work
'more like a download, and less like a stream.
'Although you are using a file as your source, this
'is a real "stream" of data. This is important, as Windows Media Player cannot save a stream, but
'it can easily save a download. If you want to prevent easy saving and downloading, the streaming
'method is required. If you don't care, you probably don't need this script anyway.
'Don't buffer the output; send it as it goes
context.Response.Buffer = False
Const ChunkSize As Integer = 10000
Dim iStream As System.IO.Stream = Nothing
Dim Buffer(ChunkSize) As Byte
Dim CurrentLength As Integer
Dim DataToRead As Long
Try
'Open the file.
iStream = New System.IO.FileStream(FileName, System.IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
'Total bytes to read
DataToRead = iStream.Length
'Read the bytes, send chunks at a time
While DataToRead > 0
'Verify that the client is connected.
If context.Response.IsClientConnected Then
'Read the data in Buffer
CurrentLength = iStream.Read(Buffer, 0, ChunkSize)
'Write the data to the current output stream.
context.Response.OutputStream.Write(Buffer, 0, CurrentLength)
'We're not buffering output; if we were, we would flush here.
ReDim Buffer(ChunkSize) ' Clear the Buffer
DataToRead = DataToRead - CurrentLength
Else
'Prevent infinite loop if user disconnects
DataToRead = -1
End If
End While
Catch ex As Exception
'Log your errors, if you're keeping score
Finally
If IsNothing(iStream) = False Then
'Close the file stream
iStream.Close()
End If
End Try
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
PHP Streaming Code
The PHP version is a port of the same concept used in the ASP.Net section. Remember to adjust the usleep statement based on your bitrate; it can help lessen the load on your server.
<?php
//Streaming MP3 Sample
//Ben Yanis, http://www.barnyardbbs.com
//Creative Commons Attribution 2.5
//http://creativecommons.org/licenses/by/2.5/
//You could easily make this dymanic
//For example, you could pass parameters in the the querystring
$name = './StreamSample.mp3';
$fp = fopen($name, 'rb');
if (!feof($fp)) {
header("Content-Type: audio/mpeg");
header("Connection: close");
// Only WinAMP and other enlightened players care about these. Windows Media Player needs to get them from the ASX
header("icy-name: Streaming Song Title");
header("icy-url: http://www.yoursite.com");
//The script can be executing for a fairly long time, as it is sending chunks of data (with an artificial delay too).
//You could always configure this ahead of time, this is just a safety.
//This is measured in seconds; so in this case it's 5 minutes.
set_time_limit(300);
while(!feof($fp)) {
$buf = fread($fp, 8192); // Load the buffer with 8192 chunks. Adjust as required
echo $buf;
$bytesSent+=strlen($buf); // Our running count
ob_flush();
flush();
//Sleep, make it slow for localhost testing
//This is a simplistic throttling mechanism; adding a delay after every block of bytes
//In this case, a block of 8192 bytes of released every 200,000 microsecords; or every 1/5th of a second
//You can adjust this up or down, based on the bitrate of your stuff
usleep(200000);
}
}
exit;
?>