nhz-io/raat-protocol

Reversed engineering analysis

★ 0Forks 0GitHub ↗Compare

README

Roon Advanced Audio Transport (RAAT) Protocol Specifications

Warning

Disclaimer: This document is the result of reverse engineering analysis and is NOT official documentation. It is published for educational purposes only, as anyone could repeat such an exercise.

This document provides a detailed technical specification of the Roon Advanced Audio Transport (RAAT) protocol, reconstructed from reverse-engineering the Roon Bridge and Roon Server codebases.

RAAT is Roon’s proprietary, high-performance audio transport protocol designed for bit-perfect audio streaming, low latency, and sample-accurate multi-room synchronization.


1. Network Discovery Layer: SOOD (Sooloos Over UDP Discovery)

Before establishing an audio stream, the Roon Core discovers endpoints on the local network using the Sooloos Discovery (SOOD) protocol.

Transport Details

  • Protocol: UDP
  • Port: 9003 (unicast & multicast)
  • Multicast IP: 239.255.90.90
  • Network Fallback: If multicast is blocked by network infrastructure, the Core automatically performs "ARP trickling" (iterating and sending unicast UDP queries directly to IPs in the local ARP table and subnet ranges).

Packet Layout

A SOOD packet consists of a magic header followed by a version number, message type, and a sequence of length-prefixed key-value attributes:

Offset (Bytes) Size (Bytes) Field / Value Description
0 - 3 4 "SOOD" (ASCII) Protocol Magic Identifier
4 1 0x02 (byte) Protocol Version
5 1 0x51 ('Q') or 0x52 ('R') Message Type: Query (Q / 81) or Response (R / 82)
6... Variable Key-Value Pairs Payload Attributes

Key-Value Pair Encoding:

Each attribute in the payload is serialized as:

  1. Key Length: 1 byte (uint8)
  2. Key String: UTF-8 encoded string (length specified above)
  3. Value Length: 2 bytes (uint16, big-endian)
  4. Value String: UTF-8 encoded string (length specified above). A length of 0xFFFF indicates null.

RAAT Discovery Handshake

For a discovered endpoint to be identified as a RAAT device, its SOOD response must contain a matching RAAT Service Identifier:

  • RAAT Service UUID: 5e2042ad-9bc5-4508-be92-ff68f19bdc93 (under the key service_id)

Key fields parsed from a RAAT endpoint's discovery response:

  • unique_id: Unique identifier of the device.
  • tcp_port: The TCP port the device is listening on for control connections.
  • vendor & model: Device descriptors.
  • protocol_version & raat_version: Version compatibility flags.
  • code_signing_keys: Signature validation keys.

2. Session Control Layer: JSON-over-TCP

Once an endpoint's TCP port is discovered via SOOD, the Core opens a TCP connection to establish a control session.

Control Framing (8-Byte Header)

All control packets exchanged over the TCP session share an 8-byte big-endian framing header:

Offset (Bytes) Size (Bytes) Type / Format Description
0 - 3 4 uint32 (big-endian) Message Length: Full message size (including these 8 bytes)
4 - 7 4 uint32 (big-endian) Message Type: Control code identifying the frame action
8... Variable Binary / UTF-8 Payload: Frame message body

Message Type Codes

Low-level control commands use specific uint32 message type identifiers (most control types set the high bit 0x80000000):

  • 0x80000001 (LL_REQUEST): Low-level Request. Payload layout:
    • 0 - 3: 4-byte big-endian Request ID (rid).
    • 4...: UTF-8 encoded JSON string of request arguments.
  • 0x80000002 (LL_RESPONSE): Low-level Response. Payload layout:
    • 0 - 3: 4-byte big-endian Response ID (rid) matching the request.
    • 4: 1-byte flag indicator (body[4] & 1 means this is the final chunk).
    • 5...: UTF-8 encoded JSON string of the response payload.
  • 0x80000003 (LL_KEEPALIVE): Heartbeat ping. Sent periodically to verify the session is active.
  • 0x80000004 (LL_BEGIN_KEEPALIVE): Initiates the keepalive monitoring loop.
  • 0x80000005 (LL_END_KEEPALIVE): Terminates keepalive monitoring.

Orchestration & Security Bootstrapping

When the session connects, Roon Core performs a security bootstrap:

  1. Lua Script Loading: Roon Core pushes several Lua script modules over TCP using the load_script command (e.g., base, dkjson, protocol, roon_tcp).
  2. Code Signing: The Lua scripts must be signed with Roon's private key. The Core transmits the public keypair name and RSA-SHA256 signature in the JSON request. The endpoint validates the signature against its built-in code signing keys before loading the script.
  3. Core Query: The Core then queries the device capabilities using the JSON request "request": "info".

3. Precision Clock Synchronization Layer

To support multi-device grouped zones with sample-accurate alignment, RAAT operates a precision time synchronization loop.

Transport Details

  • Protocol: UDP
  • Port: Dynamically negotiated (Core starts a UDP listener on a random port and passes it to the receiver via the TCP sync control command).

UDP Sync Exchange Packets

Once the receiver learns the Core's clock port, it enters a UDP exchange.

Clock Query Packet (RAATCLKQ)

Sent by the client (receiver or sender) to query time:

  • 0 - 7: "RAATCLKQ" (ASCII)
  • 8 - 11: 4-byte big-endian query ID (uint32).

Clock Response Packet (RAATCLKR)

Sent in response to a query:

  • 0 - 7: "RAATCLKR" (ASCII)
  • 8 - 11: 4-byte big-endian query ID (uint32).
  • 12 - 19: 8-byte big-endian remote clock ticks (uint64 / int64 in 10-nanosecond units).
  • 20 - 27: 8-byte big-endian output hardware delay value (uint64 / int64).

Synchronization Algorithm (NTP/PTP-like)

  1. The Core captures xmit_time (local monotonic ticks) and sends RAATCLKQ.
  2. On receipt of RAATCLKR, the Core captures recv_time.
  3. RTT Calculation: Round Trip Time (RTT) is calculated: $$\text{RTT} = \frac{\text{recv_time} - \text{xmit_time}}{2}$$
  4. Local Alignment: The estimated local time when the remote sampled its clock is: $$\text{local_sample} = \text{xmit_time} + \text{RTT}$$
  5. Clock Offset: The estimated clock offset is: $$\text{offset} = \text{remote_time} - \text{local_sample}$$
  6. Outlier Filtering: The Core fires up to 200 queries, keeps the 3 iterations with the lowest RTT (least network latency jitter), and uses the best offset to set _clock_offset. It continually runs this loop to monitor and compensate for average drift over time.

4. Audio Transmission Layer: TCP Binary Stream

Once the control and clock paths are synced, Roon Core sets up the audio stream.

Configuration Handshake (setup)

The Core sends a "request": "setup" command over the TCP control path specifying the stream format:

{
  "request": "setup",
  "format": {
    "sample_type": "pcm" | "dsd",
    "sample_rate": 44100,
    "bits_per_sample": 16,
    "channels": 2,
    "sample_subtype": "mqa" | "mqa_core" | "none",
    "mqa_original_sample_rate": 192000
  },
  "wire_codec": "flac" | null
}

The receiver initializes its audio hardware and responds with:

  • clock_port: The UDP port used for the clock synchronization described in Section 3.
  • audio_port_tcp: The TCP port the receiver opened to accept audio stream connections.

The Core then opens a dedicated TCP socket to audio_port_tcp.

Streaming Protocol Commands

The audio streaming socket accepts structured binary commands. Each command starts with a 1-byte Command ID followed by its arguments and data payload:

+------------+--------------------+---------------------+
| Command ID | Payload Size (L)   | Command Payload     |
| (1 Byte)   | (Big-Endian uint32)| (Variable Size L)   |
+------------+--------------------+---------------------+

COMMAND_DATA (0x00) - Audio Frame Streaming

Carries actual audio samples.

  • Byte 0: 0x00
  • Bytes 1 - 4: nsamples (big-endian uint32) - Number of audio samples.
  • Bytes 5 - 8: nbytes (big-endian uint32) - Payload byte size.
  • Bytes 9...: Audio sample bytes (uncompressed PCM/DSD inter-leaved frames).

COMMAND_CHMAP (0x01) - Channel Mapping Mask

Informs the device of active audio channels.

  • Byte 0: 0x01
  • Bytes 1 - 4: channel_mask (big-endian uint32 bitmask of active audio channels).

COMMAND_STREAMID (0x02) - Active Stream Identifier

Ties streaming frames to a control session stream.

  • Byte 0: 0x02
  • Bytes 1 - 4: stream_id (big-endian uint32).

COMMAND_STREAMSAMPLE (0x03) - Frame Sample Synchronization Index

Sets the starting frame alignment sample count.

  • Byte 0: 0x03
  • Bytes 1 - 8: streamsample (big-endian uint64 index).

COMMAND_NORMALIZATION (0x04) - Volume Normalization

Transmits normalization gain/peak details.

  • Byte 0: 0x04
  • Bytes 1 - 8: gain (double precision float serialized as 64-bit int bits).
  • Bytes 9 - 16: peak (double precision float serialized as 64-bit int bits).

COMMAND_CODEC_DATA (0x05) - Compressed Audio Streaming

To conserve network bandwidth, RAAT supports streaming audio compressed on the fly (typically as FLAC).

  • Byte 0: 0x05
  • Bytes 1 - 4: nsamples (big-endian uint32).
  • Bytes 5 - 8: nbytes (big-endian uint32).
  • Bytes 9...: FLAC-encoded audio frames.

5. Playback Coordination & Synchronization

To start playback, the Core issues a JSON start request over the control session:

{
  "request": "start",
  "time": 6829103948500,
  "stream_sample": 0,
  "min_offset": 50000000
}
  • time: The exact, absolute monotonic clock tick (in 10ns ticks) when the receiver's DAC should output the first audio sample of stream_sample.
  • stream_sample: The sample index mapping to the start boundary.

By calculating clock offsets across all endpoints, Roon Core schedules the time value in the future such that all devices output sample index 0 at the exact same physical moment, achieving flawless, drift-corrected multi-room synchronization.

Contributors

itsnein

Issues