Skip to content

Instantly share code, notes, and snippets.

@ePirat
Last active November 7, 2024 19:07
Show Gist options
  • Save ePirat/adc3b8ba00d85b7e3870 to your computer and use it in GitHub Desktop.
Save ePirat/adc3b8ba00d85b7e3870 to your computer and use it in GitHub Desktop.
Icecast Protocol specification

Icecast protocol specification

What is the Icecast protocol?

When speaking of the Icecast protocol here, actually it's just the HTTP protocol, and this document will explain further how source clients need to send data to Icecast.

HTTP PUT based protocol

Since Icecast version 2.4.0 there is support for the standard HTTP PUT method. The mountpoint to which to send the data is specified by the URL path.

Authentication

The authentication is done using HTTP Basic auth. To quickly sum it up how it works: The client needs to send the Authorization header to Icecast, with a value of Basic (for basic authentication) followed by a whitespace and then the username and password separated by a colon : encoded as Base64.

Specifying mountpoint information

The mountpoint itself is specified as the path part of the URL.
Additional mountpoint information can be set using specific (non-standard) HTTP headers:

ice-public
For a mountpoint that doesn't has <public> configured, this influences if the mountpoint shoult be advertised to a YP directory or not.
Value can either be 0 (not public) or 1 (public).
ice-name
For a mountpoint that doesn't has <stream-name> configured, this sets the name of the stream.
ice-description
For a mountpoint that doesn't has <stream-description> configured, this sets the description of the stream.
ice-url
For a mountpoint that doesn't has <stream-url> configure, this sets the URL to the Website of the stream. (This should _not_ be the Server or mountpoint URL)
ice-genre
For a mountpoint that doesn't has <genre> configure, this sets the genre of the stream.
ice-bitrate
This sets the bitrate of the stream.
ice-audio-info
A Key-Value list of audio information about the stream, using = as separator between key and value and ; as separator of the Key-Value pairs.
Values must be URL-encoded if necessary.
Example: samplerate=44100;quality=10%2e0;channels=2
Content-Type
Indicates the content type of the stream, this must be set.

Sending data

Data is sent as usual in the body of the request, but it has to be sent at the right timing. This means if the source client sends data to Icecast that is already completely avaliable, it may not sent all the data right away, else Icecast will not be able to keep up. The source client is expected to sent the data as if it is live. Another important thing to note is that Icecast currently doesn't support chunked transfer encoding!

Common status codes

Icecast reponds with valid HTTP Status codes, and a message, indicating what was wrong in case of error. In case of success it sends status code 200 with message OK. Any HTTP error can happen. This is an not exhaustive list, might change in future versions, listing most common status codes and possible errors.

200 OK : Everything ok

100 Continue : This is sent in case a Request: 100-continue header was sent by the client and everything is ok. It indicates that the client can go on and send data.

401 You need to authenticate : No auth information sent or credentials wrong.

403 Content-type not supported : The supplied Content-Type is not supported by Icecast.

403 No Content-type given : There was no Content-Type given. The source client is required to send a Content-Type.

403 internal format allocation problem : There was a problem allocating the format handler, this is an internal Icecast problem.

403 too many sources connected : The configured source client connection limit was reached and no more source clients can connect at the moment.

403 Mountpoint in use : The mountpoint the client tried to connect too is already used by another client.

500 Internal Server Error : An internal Icecast error happened, there is nothing that the client can do about it.

If anything goes wrong, the source client should show a helpful error message, so that it's known what happened. Do not shows generic messages like "An error has occured" or "Connection to Icecast failed" if it is possible to provide more details. It is good practice to always display the code and message to the user.

For example, a good error message for 403 Mountpoint in use would be: "Couldn't connect to Icecast, because the specified mountpoint is already in use. (403 Mountpoint in use)"

HTTP SOURCE based protocol

Older Icecast servers prior to 2.4.0 used a custom HTTP method for source clients, called SOURCE. It is nearly equal to the above described PUT method, but doesn't has support for the 100-continue header. The SOURCE method is deprecated since 2.4.0 and should not be used anymore. It will propably be removed in a future version.

Which method to use

Since the old SOURCE method is deprecated, a client should try both, first PUT and then fall back to SOURCE if the PUT method doesn't work.

In case of the PUT method being used with older Icecast versions that do not support it (< 2.4.0), Icecast will return an empty reply, this means, no status code or headers or body is sent.

Example request

< Indicates what is sent from the server to the client
> Indicates what is sent from the client to the server

PUT

> PUT /stream.mp3 HTTP/1.1
> Host: example.com:8000
> Authorization: Basic c291cmNlOmhhY2ttZQ==
> User-Agent: curl/7.51.0
> Accept: */*
> Transfer-Encoding: chunked
> Content-Type: audio/mpeg
> Ice-Public: 1
> Ice-Name: Teststream
> Ice-Description: This is just a simple test stream
> Ice-URL: http://example.org
> Ice-Genre: Rock
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
< Server: Icecast 2.5.0
< Connection: Close
< Accept-Encoding: identity
< Allow: GET, SOURCE
< Date: Tue, 31 Jan 2017 21:26:37 GMT
< Cache-Control: no-cache
< Expires: Mon, 26 Jul 1997 05:00:00 GMT
< Pragma: no-cache
< Access-Control-Allow-Origin: *
> [ Stream data sent by cient ]
< HTTP/1.0 200 OK

SOURCE

> SOURCE /stream.mp3 HTTP/1.1
> Host: example.com:8000
> Authorization: Basic c291cmNlOmhhY2ttZQ==
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Type: audio/mpeg
> Ice-Public: 1
> Ice-Name: Teststream
> Ice-Description: This is just a simple test stream
> Ice-URL: http://example.org
> Ice-Genre: Rock
> 
< HTTP/1.0 200 OK
< Server: Icecast 2.5.0
< Connection: Close
< Allow: GET, SOURCE
< Date: Tue, 31 Jan 2017 21:26:13 GMT
< Cache-Control: no-cache
< Expires: Mon, 26 Jul 1997 05:00:00 GMT
< Pragma: no-cache
< Access-Control-Allow-Origin: *
< 
> [ Stream data sent by cient ]
@ePirat
Copy link
Author

ePirat commented May 31, 2017

Metadata is expected to be embedded into the stream. For legacy formats (MP3, AAC...) metadata can't be included in the stream, so it has to be updated out of band using the metadata update endpoint in Icecast admin to do so.

@niko
Copy link

niko commented Sep 1, 2017

@ePirat: Are you sure about the HTTP headers ice-public etc.? Isn't it icy-public with a y instead of an e?

EDIT: Answering my on question: ice-public etc. above refers to headers present in the PUT or SOURCE request, not icecasts response to the GET request.

EDIT 2: Expanding my own answer to myself: It seems, Icecast support many formats of these headers (see https://github.com/xiph/Icecast-Server/blob/master/src/source.c). For example all these are synonyms: ice-public, icy-pub, x-audiocast-public, icy-public.

@niko
Copy link

niko commented Sep 1, 2017

I'm trying to mimic icecast behavior with a (more or less) standard HTTP server. The goal would be to be compatible to libshout. I'm answering the SOURCE requests with 200 OK. libshout bails out stating "Error during send: Libshout reported send error, disconnecting: Socket error" after a second or two. I'm using ices0 and a ruby-libshout wrapper with libshout 2.4.1.

Ignoring the data speed issue I can use curl to push mp3 data to the server successfully:

curl -XSOURCE -H "Content-Type: audio/mpeg" -d '@some.128.mp3' http://localhost:4000/mount

If I understand your discussion with @jukkagrao the server responds with a 200 and only after that the body of the request is transfered? How is that even possible?

@niko
Copy link

niko commented Sep 6, 2017

I'm one step further. I think my main obstacle now is that ices0 doesn't send a Content-Length: … or a transfer-encoding: chunked header. My server refuses to read bodies from requests which don't include one of them. bummer.

@ePirat
Copy link
Author

ePirat commented Oct 1, 2017

@niko

If I understand your discussion with @jukkagrao the server responds with a 200 and only after that the body of the request is transfered?

It depends on the Icecast version and used protocol.
In recent versions when using HTTP PUT with Transfer-Encoding: chunked, this is actually possible, as the Client can indicate end of body data to the server by other means then closing the connection, so in theory, a client could still get the 200 response. For a client though the most important is to handle errors correctly. The reason this change is documented is that this was wrong before and not according to the HTTP specification, as Icecast was sending the 200 before it actually had the body data, which does not make much sense.

Note that:

For example all these are synonyms: ice-public, icy-pub, x-audiocast-public, icy-public.

is not really true, it is how this is handled right now, but the ones for the Icecast protocol are the ice- ones.
ICY is related to the ICY Protocol, which is used by Shoutcast, and for x-audiocast I have no idea where they were used, it is still there for compatibility reasons though.

@DiscoNova
Copy link

DiscoNova commented Aug 30, 2018

While I feel like necromancer responding to such old comments that quite likely have been figured out nearly a year ago ...

@niko

If I understand your discussion with @jukkagrao the server responds with a 200 and only after that the body of the request is transfered? How is that even possible?

This was one of the shortcomings of using the "SOURCE"-method; it behaved in a way your run-of-the-mill standard HTTP-server was not expecting it to. The server expected you to disconnect after the initial response, not (as happens with IceCast) to wait for the response and then start sending more data. This is pretty much the whole reason why "PUT"-method was implemented - in order to have better compatibility with "normal" servers hoping to support IceCast in addition to anything else they supported.

Hope this helps you - or is at least of some small help to anyone trying to figure out why things aren't working the way they expect them to ... I certainly feel it would've made my life easier a decade ago when I was trying to figure the protocol out without any kind of proper documentation ;)

@Palakis
Copy link

Palakis commented Jan 15, 2019

What about current song information? I suppose it is transmitted out-of-band to the server's /admin/metadata endpoint, but I'd be curious to know if there's an in-band way, or an OOB way that doesn't require admin credentials.

@preenu91
Copy link

preenu91 commented Aug 2, 2019

How to access data from icecast?

@ePirat
Copy link
Author

ePirat commented Aug 2, 2019

@Palakis Yes, the out of band metadata are only used for "unsupported" formats like MP3 and AAC (ADTS), which have no other way to transmit metadata. For Ogg and WebM it is expected that the source client muxes the metadata into the container, and in fact, the OOB endpoint does not work for these.

@danhab99
Copy link

danhab99 commented Dec 2, 2019

Is there a npm package that implements all of this already?

@ben221199
Copy link

I made a list of all ICY, Audiocast and Ice headers and the versions they appear in: https://github.com/ben221199/MediaCast.

  • ICY stands for "I Can Yell". Today the protocol is known as SHOUTcast. It is supported since the first version of Icecast until now.
  • X-Audiocast was the first version of the Icecast protocol. It was available since Icecast 1.1.4 and removed before Icecast 2.0.0. Some headers are still available in Icecast 2 for backwards compatibility.
  • Ice is the second version of the Icecast protocol. It is available since Icecast 2.0.0.

@zn3x
Copy link

zn3x commented Aug 7, 2021

For icecast client that uses put method, is there any client that uses Transfer-Encoding: chunked? I tried both vlc and ffmpeg, vlc still uses source method, and ffmpeg uses put method with 100-continue but doesn't specify no Transfer-Encoding or Content-Length and begin to transmit packets like in a source request after receiving response header from server with 100 status code.

@owenashurst
Copy link

Does anyone have any example source code demonstrating connecting and streaming? Doesn't matter what language.

@owenashurst
Copy link

@owenashurst https://gist.github.com/wtrsltnk/9a50d0824b5e5b82d96d718003e6fc47

That's to Shoutcast, doesn't seem to work with Icecast2.

I got as far as streaming when playing around, but it seems like it's not actively "playing" the file, instead it's sending it too quickly.

@wtrsltnk
Copy link

For me this is working with icecast 2.4 on windows. The timing at which the data is send to the icecast server is important. Try adjusting "Thread.Sleep(50);" an see what is working for your setup. If you want to send data at the correct speed, you'll have to calculate this based on the bit rate of the input file and limit the speed based on your calculations.

@owenashurst
Copy link

owenashurst commented May 12, 2024

For anyone wanting something more dynamic that dynamically changes the Thread.Sleep based on the bitrate, this is something I hacked together in LINQPad. It uses ffmpeg to get the bitrate of the file since I couldn't get an accurate bitrate when trying to parse the MP3 myself.

Upon testing, if you stop the media client that's "listening" from Icecast, and start playing again, you'll notice it is indeed playing the actual file. This seems more stable.

void Main()
{
    // Icecast server details
    string icecastHost = "192.168.0.100";
    int icecastPort = 9000;
    string icecastMountPoint = "/csharpstream";

    // Icecast server authentication credentials
    string icecastUsername = "source";
    string icecastPassword = "SourcePasswordHere";

    // MP3 file to stream
    string mp3FilePath = @"C:\Music\SomeMusicFile.mp3";

    try
    {
        // Connect to Icecast server
        TcpClient icecastClient = new TcpClient(icecastHost, icecastPort);
        NetworkStream icecastStream = icecastClient.GetStream();

        string authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{icecastUsername}:{icecastPassword}"));

        // Prepare HTTP headers
        string headers = $"PUT {icecastMountPoint} HTTP/1.1\r\n" +
                             $"Host: {icecastHost}:{icecastPort}\r\n" +
                             "User-Agent: IcecastStreamer\r\n" +
                             "Authorization: Basic " + authHeader + "\r\n" +
                             "Content-Type: audio/mpeg\r\n" +
                             "Transfer-Encoding: chunked\r\n" +
                             "ice-public: 1\r\n" +
                             "ice-name: YourStreamName\r\n" +
                             "ice-description: YourStreamDescription\r\n" +
                             "ice-url: http://your-stream-website.com\r\n" +
                             "ice-genre: YourStreamGenre\r\n\r\n";

        // Send headers to Icecast server
        byte[] headerBytes = System.Text.Encoding.ASCII.GetBytes(headers);
        icecastStream.Write(headerBytes, 0, headerBytes.Length);

        var parsedBitrate = GetBitrate(mp3FilePath);
        
        Console.WriteLine("Starting stream...");

        using (var mp3Stream = File.OpenRead(mp3FilePath))
        {
            var bufferSize = 8192;
            var buffer = new byte[bufferSize];
            int bytesRead;
            var bitrate = parsedBitrate;
            var sleepDuration = (int)((bufferSize * 8 * 1000) / (bitrate * 1000)); // Calculate sleep duration based on buffer size and bitrate

            while ((bytesRead = mp3Stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                icecastStream.Write(buffer, 0, bytesRead);
                Thread.Sleep(sleepDuration);
            }
        }

        // Close the connection
        icecastStream.Close();
        icecastClient.Close();

        Console.WriteLine("Streaming completed successfully.");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
}

static int GetBitrate(string filePath)
{
    try
    {
        // Create a ProcessStartInfo object to configure the process
        ProcessStartInfo startInfo = new ProcessStartInfo
        {
            FileName = @"C:\ffmpeg\bin\ffprobe",
            Arguments = $"-v error -select_streams a:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 \"{filePath}\"",
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        // Create a new process and start it
        using (Process process = new Process { StartInfo = startInfo })
        {
            process.Start();

            // Read the output of ffprobe
            string output = process.StandardOutput.ReadToEnd();

            // Wait for the process to exit
            process.WaitForExit();

            // Parse the bitrate from the output
            if (int.TryParse(output, out int bitrate))
            {
                var bitrateInkbps = bitrate / 1000;
                Console.WriteLine($"Bitrate: {bitrateInkbps} kbps");
                return bitrateInkbps;
            }
            else
            {
                throw new Exception("Failed to parse bitrate");
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
        throw;
    }
}

@MrKhay
Copy link

MrKhay commented Aug 29, 2024

This section of your header (Transfer-Encoding: chunked) ICECAST currently does not support it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment