From 9d7e08b439379d393af59ab15fc9fcd2be31b387 Mon Sep 17 00:00:00 2001 From: Brian Daniels Date: Sun, 19 Jan 2020 10:05:49 -0600 Subject: [PATCH] Add an initial binary protocol doc. --- README.md | 10 ++-- doc/binary_protocol.md | 128 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 doc/binary_protocol.md diff --git a/README.md b/README.md index 3db6173e..a87a4cb6 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ Snapcast ![Snapcast](https://raw.githubusercontent.com/badaix/snapcast/master/doc/Snapcast_800.png) -**S**y**n**chronous **a**udio **p**layer - +**S**y**n**chronous **a**udio **p**layer + [![Build Status]( https://travis-ci.org/badaix/snapcast.svg?branch=master)](https://travis-ci.org/badaix/snapcast) [![Github Releases](https://img.shields.io/github/release/badaix/snapcast.svg)](https://github.com/badaix/snapcast/releases) @@ -30,6 +30,8 @@ Every received chunk is first decoded and added to the client's chunk-buffer. Kn Typically the deviation is smaller than 1ms. +For more information on the binary protocol, please see the [documentation](doc/binary_protocol.md). + Installation ------------ You can either build and install snapcast from source, or on Debian systems install a prebuilt .deb package @@ -98,9 +100,9 @@ stream = file:///home/user/Musik/Some%20wave%20file.wav?name=File ``` The pipe stream (`stream = pipe`) will per default create the pipe. Sometimes your audio source might insist in creating the pipe itself. So the pipe creation mode can by changed to "not create, but only read mode", using the `mode` option set to `create` or `read`: - + stream = pipe:///tmp/snapfifo?name=Radio&mode=read" - + Test ---- You can test your installation by copying random data into the server's fifo file diff --git a/doc/binary_protocol.md b/doc/binary_protocol.md new file mode 100644 index 00000000..ee86b808 --- /dev/null +++ b/doc/binary_protocol.md @@ -0,0 +1,128 @@ +# Snapcast binary protocol + +Each message sent with the Snapcast binary protocol is split up into two parts: +- A base message that provides general information like time sent/received, type of the message, message size, etc +- A typed message that carries the rest of the information + +## Client joining process + +When a client joins a server, the following exchanges happen + +1. Client opens a TCP socket to the server (default port is 1704) +1. Client sends a [Hello](#hello) message +1. Server sends a [Server Settings](#server-settings) message +1. Server sends a [Stream Tags](#stream-tags) message +1. Server sends a [Codec Header](#codec-header) message + 1. Until the server sends this, the client shouldn't play any [Wire Chunk](#wire-chunk) messages +1. The server will now send [Wire Chunk](#wire-chunk) messages, which can be fed to the audio decoder. +1. When it comes time for the client to disconnect, the socket can just be closed. + +## Messages + +| Typed Message ID | Name | Notes | +|------------------|--------------------------------------|---------------------------------------------------------------------------| +| 0 | [Base](#base) | The beginning of every message containing data about the typed message | +| 1 | [Codec Header](#codec-header) | The codec-specific data to put at the start of a stream to allow decoding | +| 2 | [Wire Chunk](#wire-chunk) | A part of an audio stream | +| 3 | [Server Settings](#server-settings) | Settings set from the server like volume, latency, etc | +| 4 | [Time](#time) | Used for synchronizing time with the server | +| 5 | [Hello](#hello) | Sent by the client when connecting with the server | +| 6 | [Stream Tags](#stream-tags) | Metadata about the stream for use by the client | + +### Base + +| Field | Type | Description | +|-----------------------|--------|---------------------------------------------------------------------------------------------------| +| type | uint16 | Should be one of the typed message IDs | +| id | uint16 | Used in requests to identify the message (not always used) | +| refersTo | uint16 | Used in responses to identify which request message ID this is responding to | +| received.sec | int32 | The second value of the timestamp when this message was received. Filled in by the receiver. | +| received.usec | int32 | The microsecond value of the timestamp when this message was received. Filled in by the receiver. | +| sent.sec | int32 | The second value of the timestamp when this message was sent. Filled in by the sender. | +| sent.usec | int32 | The microsecond value of the timestamp when this message was sent. Filled in by the sender. | +| size | uint32 | Total number of bytes of the following typed message | + +### Codec Header + +| Field | Type | Description | +|------------|---------|-------------------------------------------------------------| +| codec_size | unint32 | Length of the codec string (not including a null character) | +| codec | char[] | String describing the codec (not null terminated) | +| size | uint32 | Size of the following payload | +| payload | char[] | Buffer of data containing the codec header | + +### Wire Chunk + +| Field | Type | Description | +|----------------|---------|---------------------------------------------------------------------------------------| +| timestamp.sec | int32 | The second value of the timestamp when this part of the stream was recorded | +| timestamp.usec | int32 | The microsecond value of the timestamp when this part of the stream was recorded | +| size | uint32 | Size of the following payload | +| payload | char[] | Buffer of data containing the codec header | + +### Server Settings + +| Field | Type | Description | +|---------|--------|----------------------------------------------------------| +| size | uint32 | Size of the following JSON string | +| payload | char[] | JSON string containing the message (not null terminated) | + +Sample JSON payload (whitespace added for readability): + +```json +{ + "bufferMs": 1000, + "latency": 0, + "muted": false, + "volume": 100 +} +``` + +- `volume` can have a value between 0-100 inclusive + +### Time + +| Field | Type | Description | +|----------------|---------|------------------------------------------------------------------------| +| latency.sec | int32 | The second value of the latency between the server and the client | +| latency.usec | int32 | The microsecond value of the latency between the server and the client | + +### Hello + +| Field | Type | Description | +|---------|--------|----------------------------------------------------------| +| size | uint32 | Size of the following JSON string | +| payload | char[] | JSON string containing the message (not null terminated) | + +Sample JSON payload (whitespace added for readability): + +```json +{ + "Arch": "x86_64", + "ClientName": "Snapclient", + "HostName": "my_hostname", + "ID": "00:11:22:33:44:55", + "Instance": 1, + "MAC": "00:11:22:33:44:55", + "OS": "Arch Linux", + "SnapStreamProtocolVersion": 2, + "Version": "0.17.1" +} +``` + +### Stream Tags + +| Field | Type | Description | +|---------|--------|----------------------------------------------------------------| +| size | uint32 | Size of the following JSON string | +| payload | char[] | JSON string containing the message (not null terminated) | + +Sample JSON payload (whitespace added for readability): + +```json +{ + "STREAM": "default" +} +``` + +[According to the source](https://github.com/badaix/snapcast/blob/master/common/message/stream_tags.hpp#L55-L56), these tags can vary based on the stream.