Skip to content

Instantly share code, notes, and snippets.

@niko
Forked from ePirat/spec.md
Last active April 25, 2024 23:43
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niko/2a1d7b2d109ebe7f7ca2f860c3505ef0 to your computer and use it in GitHub Desktop.
Save niko/2a1d7b2d109ebe7f7ca2f860c3505ef0 to your computer and use it in GitHub Desktop.
Icecast Protocol specification

An collection of documents about icecast/shoutcast streaming.

metaint = ARGV.first && ARGV.first.to_i || 16000
STDERR.puts "metaint: #{metaint}"
i = 0
started_at = Time.now
streamed_bytes = 0
while b = STDIN.read(1)
if i == metaint
metalength = b.bytes.first.to_i * 16
meta = STDIN.read metalength
meta.force_encoding 'utf-8'
STDERR.puts "#{metalength}:#{meta.bytesize} #{(streamed_bytes/(Time.now - started_at)).to_i}b/s #{meta}"
i = 0
else
STDOUT.print b
i += 1
streamed_bytes += 1
end
end

Transmittion metadata within the stream from an icecast server to a client basically works like this:

  • The client adds an HTTP header to the request indicating that it is able to process in-band metadata: Icy-Metadata: 1.
  • The server may then splice the metadata into the stream. If it does so, it has to tell the client the frequency with wich metadata is added. That's also done via an HTTP header: icy-metaint: 16000. 16000 is a safe bet. All clients I've seen are OK with that. For higher precision with known clients (for example when relaying a stream) you can try to use 1000.
  • The metadata then gets just cut into the stream every icy-metaint bytes.
  • The metadata block starts with a single byte indicating how long the metadata is. This byte must be multiplied with 16 for the actual size. Then the metadata follows.
  • The metadata itself may have several fields, but the only crucial one is StreamTitle. I have yet to see a client that supports anything else.

So a request/response may basically look like this:

$ curl -i -H 'Icy-Metadata: 1' http://stream.domain.com/mountpoint
HTTP/1.1 200 OK
Date: Mon, 14 Dec 2020 14:19:59 GMT
Content-Type: audio/mpeg
Transfer-Encoding: chunked
Connection: keep-alive
icy-name: Mountpoint
icy-url: http://domain.com/mountpoint
icy-description: My webradio station
icy-genre: Pop
icy-pub: 1
icy-sr: 44100
icy-audio-info: ice-channels=2;ice-samplerate=44100;ice-bitrate=128
Server: whatevercast 12.5
icy-br: 128
icy-metaint: 16000

xxx then here comes 16k of mp3 bytes …
02StreamTitle='A nice song';StreamUrl=''00000       <- padded with 0-bytes up to 2*32 bytes of metadata
xxx then here comes another 16k of mp3 bytes …
0                                                   <- the metadata usually only gets sent once
xxx then here comes yet another 16k of mp3 bytes …
…

The most unsophisticated way to extract the first metadata block from stream with icy-metaint: 16000 looks like this:

curl -s -H 'Icy-Metadata: 1' stream.domain.com/mountpoint | head -c 16128 | tail -c 128 | hexdump -C
00000000  03 53 74 72 65 61 6d 54  69 74 6c 65 3d 27 44 69  |.StreamTitle='Di|
00000010  65 20 46 61 6e 74 61 73  74 69 73 63 68 65 6e 20  |e Fantastischen |
00000020  56 69 65 72 20 2d 20 52  65 69 63 68 27 3b 00 00  |Vier - Reich';..|
00000030  00 82 98 b0 e1 45 48 58  3c 01 e6 79 0a af 15 50  |.....EHX<..y...P|
00000040  66 52 c1 b7 f0 f3 45 4f  27 21 0e 0a 19 2f a9 24  |fR....EO'!.../.$|
00000050  93 24 88 24 10 e7 98 e6  3f 3e cb 53 af ad c0 00  |.$.$....?>.S....|

Here you can see the first byte 3 indicating that 3*16 = 48 bytes of metadata are following. The metadata has the important StreamTitle field which ends with ';. The rest of the 48 bytes is 0-padded.

A very basic icy metadata parser in ruby looks like the attached icy.rb. It consumes a stream on STDIN, outputs metadata and information on STDERR and the actuall mp3 data on STDOUT. I can be used like this:

curl -s -H 'Icy-Metadata: 1' 'http://stream.domain.com/mountpoint' | ruby icy.rb | mpg123 -

Note that you can (and have to) pass a metaint different from 16000 as first argument to the ruby script.

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 ]
@norohind
Copy link

norohind commented Jan 7, 2023

Thanks for code example!

@angrycoding
Copy link

Thank you very much for it :)

Copy link

ghost commented May 8, 2023

Thank you for this!

@MrJake222
Copy link

best!

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