Intona products are engineered and made in Germany. We ship daily worldwide.




AVALOT is a AES67 compatible network audio device

AVALOT devices receive and generate network audio streams that meet the AES67 standard. The configuration is easily and portably done in a human-readable JSON format over UDP or UART.
Some design decisions have been made which will be discussed briefly in the following.

Mediaclock, phase-coherence and PTP

As a receiver, the media clock is always extrapolated from the actual audio stream. The separately received PTP time is used exclusively for aligning the link offset, which ultimately ensures the phase coherence of all devices on the network with each other. The advantage of this procedure is that even in the absence or failure of the PTP system, there will be no disruption to the audio playback. Therefore, there are multiple lock levels. The audio playback is interrupted only if RTP lock is lost. However, this is only the case if the data stream does not arrive at the device.


SAP (Session Announcement Protocol) is required for AVALOT, but optional in AES67. Other session management protocols may be supported in the future.

SAP Caching

AVALOT devices send out cached SAPs at a relatively high rate. The SAP was specified for old network technology and prioritized reducing traffic. AVALOT devices are likely used in environments where every second can count. This "SAP replication" can help if devices need to be setup very quickly. Likewise, SAP timeouts were reduced to avoid listing stale sessions.

Session Identification

The stream name (SDP "s" field) is used to uniquely identify sessions. Duplicate names are considered an invalid misconfiguration

IGMP in consumer switches

IGMP is the protocol for controlling multicasts in the network. The state of IGMP implementations in many consumer switches is very poor. This situation has been thoroughly analyzed and a workaround has been implemented to improve the situation. This is supposed to help against badly implemented IGMP-snooping. (See igmp_hack.)

The API protocol

The device can be configured over network. It exchanges commands and responses as JSON objects. The main intention is that this is used as API by control programs, although you can also send and receive raw commands manually.

Each UDP packet contains one or multiple commands as JSON objects, formatted as single line of text. These UDP packets are sent to port 7054. The device will respond with an UDP packet containing JSON object, sent back to the sender's IP, MAC and UDP port.

Here is an example:

User => Device
{"command": "set_params", "api_version": 6, "seq": 123, "stream": {"name": 
"HDLM8-1302 : 8", "link_offset": 48, "output_channels": [0, 1, 2, 3] }}
Device reply
{"seq": 123}

Here the "set_params" command is used to subscribe the AES67 stream that was propagated as "HDLM8-1302 : 8". The reply indicates success, because it does not contain an "error" field. The "seq" field is for free use by the sender (but must be a JSON number), and will be sent back with the reply. It can be used to associate responses with commands.

Erroneous requests (including JSON syntax errors etc.) will result in a response with the "error" field set.

UART Interface

The hardware module can be addressed internally in the same API language via serial UART interface, with the advantage that the network connection does not have to be configured. This is specifically tailored to the OEM integrator to access the module internally, and in every situation.

Note: every packet sent over UART must be terminated with a null byte. This is also the case for all packets that AVALOT sends over UART.

Getting started

This section shows how to initialize the device and how to start audio streaming.

Finding the device on the network

Unless configured otherwise, each device uses an automatically assigned link-local IP address (RFC 3927). Addresses start at, but are generally unpredictable and unstable. For example, if a device is restarted, it may pick a different address. Usually, it will attempt to reuse the previous address, but you cannot rely on this. (You could use the set_params command to assign a static address to a device to avoid this.)

You should perform device discovery by sending device_info commands to the broadcast address ( These commands can be sent periodically to provide live updates on settings, and whether the device is still up. To avoid overloading the device, an update period of no shorter than 500 ms is recommended. This is especially important if several clients are on the network.

Use the device_id field (queried with device_info) as unique, stable device identifier.

Sending and receiving commands (via CLI on Linux/macOS/msys)

Since the protocol simply sends and receives UDP packets containing JSON text, you can send and receive commands with common open source tools:

rlwrap -S '> ' nc -u 7054 | jq

nc sends and receives UDP packets (as text in this case), rlwrap provides line editing and a history, and jq pretty prints JSON responses.

Setting up audio streaming (via CLI)

The device supports SAP (Session Announcement Protocol, RFC 2974) AES67 session discovery. Passive discovery is always active. The device does by default not play any audio from network. The DPLMX network API can be used to play a specific stream.

All important settings and information can be queried with:

User => Device
{"command": "device_info"}

Which results in this reply (only AES67 relevant parts shown):

Device reply
  "stream": {
    "name": "BKLYN-II-0d0c9a : 31",
    "link_offset": 48,
    "nominal_level_dbu": 0,
    "output_channels": [ 0, 1 ]
  "streams": {
    "list": [
        "n": "BKLYN-II-0d0c9a : 31",
        "i": "1 channels: Analog R",
        "c": 1
  "rtp": {
    "lock": 3

This example run returned two AES67 sessions. The device is streaming the first one (according to the field).

A specific session can be selected with:

User => device
{"command": "set_params", "stream": {"name": "BKLYN-II-0d0c9a : 32"}}

The set_params command has a large number of parameters, which can be used to control every detail about the device. The parameter controls which session should be selected. Internally, the name is matched against the internal list of sessions, and the first matching session is selected. By default, the name is empty, and no session matches.

The streaming status is indicated by the rtp.lock field.

Using this command persists all set parameters to the device flash memory (see section below).

Persistence and reboots

Most device settings are persistent. They are written to flash memory. After boot, the settings are restored from flash. There is a delay of about 1 second before the device starts writing the changed settings to the flash. This delay counts since the most recent change. For this reason, the device should not be power cycled immediately after a change. (The device should survive power cycles at any possible moment, but recently changed settings will not be restored.)

It can take a while until a rebooted device recovers and streams audio. The following processes incur additional delays:

The processes listed above happen in parallel. For network API access, the device needs to negotiating a link layer IP address, which takes about about 7 seconds on average. If static IP configuration is used, the API is available as soon as the network link is up.

Firmware updates require a reboot. Firmware updates can reset all or some settings. Never apply firmware updates in a production setting.

Firmware updates

The device runs a TFTP server for firmware updates. It accepts firmware binaries under the name "firmware.bin". All other files are rejected. Update can be done with any TFTP client, for example curl:

curl -T path/to/firmware.bin tftp://


API protocol definition


Network layer

The protocol uses JSON encoded in UTF-8 text, encapsulated in UDP. Each UDP packet payload contains exactly 1 JSON object (a list of fields enclosed with "{" and "}"). A JSON object cannot span multiple UDP packets. A JSON object represents a single command. The convention is to add a trailing newline character ("\n", ASCII 0x0A) at the end of the JSON text.

UDP packets are exchanged between devices and API clients. Clients send commands as UDP packets to port 7054. The device will attempt to send a response packet back to the client immediately, using the command packet's source IP/port as destination IP/port for the response packet.

The device does not support receiving fragmented IP packets. All commands are assumed to fit within 1472 bytes of UDP payload. On the other hand, responses by the device may be sent as fragmented UDP packets if they exceed the MTU.

A MTU of 1500 bytes is assumed.

Be aware that UDP is an unreliable protocol. Packet loss and reordering is possible. The device sends exactly one response for each request, and this response could get dropped by the network. If you do not receive any response, you cannot know whether the request or response was dropped (or whether the device went offline). You may have to repeat the request until you get a response (assuming the command is idempotent - most are). Since the network has to reliably transport AES67 streams, it is assumed that everything operates with some headroom below the maximum possible network bandwidth, and packet dropping does not actually happen in practice.


JSON, as used by the AVALOT API protocol, is specified by RFC 8259 and RFC 7493. The protocol requires that all top-level JSON values are JSON objects, and that each packet contains a single JSON object. In particular, it is assumed that JSON numbers use double floats (binary64 in IEEE 754-2008), which limits their integer precision to about 53 bits (see RFC 7493 section 2.2 for details).

The command reference will use the term "integer" for numbers which are representable as signed 32 bit integers (sometimes unsigned 32 bit integers).


The UDP payload consists of text that can be parsed as a single JSON object. There must be no 0 bytes or other non-text data. Whitespace can be present, but should be avoided to reduce the packet size. A final newline should be added at the end of the JSON text. (It's currently not required, but this is a threat that this may change.)

On the IP level, the command must be a single IP fragment.

Each JSON object sent to the device represents a single command. It can contain the following fields:

commandStringRequired. Name of the command to run.
seqNumberSent back with responses, and is otherwise not interpreted or changed by the device.
api_versionIntegerOptional, but passing this is highly recommended. The current protocol version is 6.
add_crcBooleanIf true, a CRC will be appended to the JSON data of the response. (See Responses
section below.)
(other)...Command specific parameters are located in the same top-level JSON object.

All fields except "command" are optional. The "api_version" should also be passed.

The most important commands are "device_info" and "set_params". All other commands are obscure and rarely needed.


Responses indicate success or failure of a previously sent command. There is exactly one response per command. (Minus dropped network packets.)

The payload contains a single line of JSON text, with redundant whitespace trimmed (clients shouldn't rely on this) and a single newline character at the end (clients can rely on this).

On the IP level, large responses are sent as multiple IP fragments. These are reassembled to a large UDP packet, whose payload follows the described format. Large responses usually happen with with "device_info" commands, and can be mostly avoided by using the "select" parameter.

The responses JSON objects contain the following fields:

seqNumberAlways present. The same value as the command packet, or 0 if it was absent.
errorStringIf present, a protocol error of some kind has happened. This is usually used for
malformed commands only. It's a string value, that contains an error message.
It is not intended to be machine-readable.
warningStringIf present, warnings or errors. This is a string value and contains newline
characters (one message per line). This is not necessarily an error. It is not
intended to be machine-readable. The main use of this field and the "error"
field is to help with debugging.
(other)...Command specific response fields are located in the same top-level JSON object.

If "add_crc" was set to true in the command, the response packet uses a slightly modified format. The four byte sequence "\n//#" (0a 2f 2f 23 in hex) is appended after the JSON payload (instead of a final "\n" byte), followed by 8 ASCII digits representing the CRC32 of the payload before the four byte header. For example, the byte sequence " 0a 2f 2f 23 61 39 34 64 61 31 65 61" at the end of the packet indicates the CRC32 value 0x a94d a1ea. If present, this byte sequence will always be 12 bytes long and end with the packet's payload. The CRC32 uses the same polynomial as Ethernet and zlib. This can be useful if fragmented UDP packets occur, and the UDP checksum is not trusted. (In single fragment cases, the packet is protected sufficiently by the Ethernet CRC.) The format was chosen such that we can claim it's still a text based protocol. Naive clients can just not set this field. Then the "\n" byte is always the last byte of the payload, and the only one in the payload.


JSON was picked as base of the AVALOT protocol to make it easier to extend the protocol in the future. For this reason, both device and clients must make the following assumptions:

Packet drops and reordering

UDP can drop or reorder packets. Clients can use the "seq" field to avoid confusion due to reordering. It can also be used to detect packet drops (for example, if no response for a command is received, the client can issue a redundant command to check whether the device is still operating). If a response is lost, the client does not know whether the command was executed, or the result status. In this case the client needs to check the device state, or send the command again to be sure.

Retrieving updates and events from a device

There is no mechanism for receiving any kind of events with the API. Poll the device by sending device_info commands. A polling period of 500 ms or higher is recommended.


API protocol reference

All commands can include standard parameter fields described in the section above. The same applies to responses. Only command-specific request/response fields are listed in the tables below.


Retrieve information about device firmware, AES67 settings/status/session list, and more.


This command reports general information about device and network status. This command should be used for device discovery.

The "set_params" command is an important companion command. It mirrors most fields in the "device_info" command, and can be used to set the corresponding fields. All references to setting fields in the parameter description above is in context of the "set_params" command.

The "select" field can be used to retrieve only some information. It is an array of strings, where each string is the name of a JSON sub-object. Only sub-objects mentioned in this list are sent in the response. For example, if you send select:[ "net"], then other sub-objects like "streams" are not sent, only the "net" sub-object. If the field is missing, everything is sent.


User => Device
{"command": "device_info", "seq": 123, "api_version": 6}
Device reply (prettified)
  "seq": 123,
  "product": "ISAAC",
  "firmware_version": "v1.5.2",
  "api_version": 6,
  "fw_date": 1654077358,
  "device_id": "00313753384d37373038303432303139",
  "product_id": 1001,
  "net": {
    "mac": "1A:7D:9C:4F:26:3A",
    "ip": "",
    "static_ip": "",
    "igmp_hack": true
  "stream": {
    "name": "AVIOUSB-5346cb : 2",
    "link_offset": 48,
    "nominal_level_dbu": 0,
    "output_channels": [ 0, 1 ]
  "streams": {
    "list": [
        "n": "AVIOUSB-5346cb : 2",
        "i": "1 channels: Left",
        "c": 1
  "rtp": {
    "lock": 0
  "ui": {
    "order": 0,
    "name": "",
    "loc": "",
    "memo": ""
  "logging": {
    "en": false




JSON arrayOptional. Each array item must be a string. Each string can be the key of a
sub-object to include in the response. If missing, all sub-objects are returned.


productStringGeneral product ID. Always "ISAAC" (read-only).

Firmware version identifier, usually a release version string (read-only).
Note: never parse this in program code! Some firmware binaries use a
git hash instead of a version number.


API revision used by device, may be different from client's version in
"api_version" field (read-only).
Note: older firmware versions do not have this field.

fw_dateIntegerUNIX-time of firmware build date (read-only).
Note: older firmware versions do not have this field.
device_idString128 bit globally unique hardware identifier in hexadecimal, for example
"00313553504e52433033303436303535". This is the MCU's builtin
UUID. It never changes, unless the MCU is physically replaced. (Read-only.)
firmware_update_errorStringOptional. If present, a human readable error description on firmware
update errors. Always cleared on reboots. (Read-only.)
product_idIntegerProduct ID (see table below). (Read-only, normally.)
hw_channelsIntegerAvailable audio channels in this hardware.
Note: older firmware versions do not have this field. Treat the value to 1
by default.
netObjectNetwork settings/state (see table below).
uiObjectInformational fields for the Network Manager GUI (see table below).
streamObjectAES67 parameters (see table below).
streamsObjectAES67 stream list (see table below).
rtpObjectAES67 state (see table below).
loggingObjectLogging control (see table below).
link_stateObjectEthernet port(s) link state (see table below).

product_id (Product IDs)

The "product_id" field contains a fixed number that is defined by Intona and used to differentiate between different types of end devices.

Raw valueName
0(Never used, might happen on major device flash corruption.)
1 .. 999Reserved for DPLMX flavoured product variation.
1000+Maintained by Intona, ask them.

net (Network settings/state)

The response "net" field is an object with the following fields:

macStringCurrent MAC address, formatted as 6 groups of 2 hexadecimal numbers
separated by ":", for example "AB:CD:12:34:56:7E". (Read-only.)
ipStringCurrent IP address formatted as quad-dotted IP address, for example
"". (Read-only.)
static_ipStringConfigured static IP address (the same format as the "ip" field), or
"" if the IP address should be dynamically allocated. Applied after
device reboot.
igmp_hackBooleanIf true, enable an IGMP workaround, that is supposed to help with
broken IGMP-snooping switches. (Enabled by default.)

ui (UI fields)

The response "ui" field is an object with the following fields:

orderIntegerOrder ID (used for GUI device list sort order).
nameStringUser-assigned device name (max. 127 bytes).
locStringUser-assigned device location text (max. 127 bytes).
memoStringText field for free use (max. 127 bytes).

The strings are supposed to be encoded as UTF-8 (usually enforced by JSON), though the device will accept any encoding in JSON or these fields.

None of the values in this object are actually interpreted by the firmware. They are for use by GUI tools.

stream (AES67 settings)

The response "stream" field is an object with the following fields:

nameStringStream name, used to select session.
link_offsetIntegerLink offset in samples.

Nominal level in dBu. The resulting gain value is
calculated using the internally stored analog
level reference in respect to 0 dBFS.

output_channelsInteger ArraySet the source channel for each hardware output,
starting from 0. Set -1 to disable the source.
If a stream source channel is unavailable, it will
be treated as "-1" and muted.
ch0 .. chnIntegerDeprecated, use "output_channels".
Set the source channel for the first hardware output,
starting from 0. Set to -1 to mute this output.

Note: there are two methods to set the source channel(s). Using of "output_channels" is recommended. If both "output_channels" and "ch0" are given, the behaviour is undefined.

Example using output_channels

User => Device
{"command": "set_params", "stream": { "output_channels": [0, 2, -1, 2] }}

Assuming four hardware outputs, the example above sets the audio sources as follows:

Hardware OutputStream Channel



streams (AES67 session list)

The response "streams" field is an object with the following fields:

listJSON arrayActual session array, see below.

Each item in the "list" array is an object with the following fields:

nStringSession name
iStringSession info
cIntegerChannel count

This is the list of discovered sessions. The session name is the "s" field in the SDP, and is compared to to select a session. The session info is the "i" field in the SDP. The channel count is also as indicated in the SDP, and gives the upper range of the stream.output_channels field (exclusive).

rtp (RTP state)

The response "rtp" field is an object with the following fields:

lockIntegerAES67 streaming state

The lock field can have the following values:

Raw valueMeaning
0No PTP, no RTP data
1PTP locked, but no RTP data
2No PTP, but RTP locked
3PTP and RTP locked

"Locked" in this context means "synchronized to". Streaming is possible at state 2 or 3. State 2 may be indicated by a blinking yellow Ethernet port LED, at state 3 it glows normally. RTP is for Real-Time Transfer Protocol, the part of AES67 that transports actual audio data over network.

logging (Logging state)

The response "logging" field is an object with the following fields:

enBooleanWhether logging is enabled

Logging is a debug feature. The log is useful to firmware developers only. Technically inclined people may be able to interpret parts of the log, but it's not recommended. Logging makes the device broadcast UDP packets to IP on port 7055. The log state is off by default, and is not restored across firmware reboots.

Log packets have a format that is slightly different from normal protocol messages. The first part of the UDP payload consists of JSON text, like in normal messages. This is followed by a 0 byte, a binary length field, and the log text.

0NJSON text of length N bytes
N + 01Literal 0 byte (N found by searching for the first 0 byte)
N + 12Little endian uint16_t log text length
N + 2lengthASCII log text, using length field above

This binary length field and log payload is found by searching for a 0 byte. JSON cannot legally contain a 0 byte.

This format was chosen because we wanted to continue using JSON for its positive properties, all while reducing device MCU load by not having to turn the log message into a legal JSON string value (escaping).

The JSON text is an object with the following fields:

commandStringContains the string "raw_log", otherwise it's not a log message
posIntegerLog byte position, can be used to detect dropped log messages
overflowBooleanInternal overflow flag (not always present)

The "pos" field is the number of log bytes output so far. The log receiver can add the packet's log text length to the "pos" field to know the "pos" field of the next log packet. If the values mismatch, a log packet must have been dropped or reordered. The value of this field is an unsigned 32 bit integer and rolls over.

"overflow" indicates that the device log buffer (which is of limited size) overflowed before the contents could be sent to the network. The "pos" field is also discontinuous in this case.

The response "link_state" is an object with the following fields:

listJSON arrayActual integer array, see below.

Each item in the "list" array is an integer value that reflects the link state from the indexed ethernet port in the hardware.

Link state values:

0No link
1100 Mbps link active
21000 Mbps link active
3+Reserved for future use

Example if hardware has two Ethernet ports with the first port being inactive and the second port having a 100 Mbps link:

Device reply (prettified)
{ "link_state": { "list": [ 0, 1 ] } }

Example if hardware has a single Ethernet port with the port having a 1000 Mbps link:

Device reply (prettified)
{ "link_state": { "list": [ 2 ] } }


Write various device settings.


This command mirrors a number of fields in the device_info response. All fields are optional, and only parameters sent with the command are actually set.

See the response fields of the device_info command for type and meaning of each field, and whether it can be written.


Success/error only. If an error happens, and the request contains multiple parameters, it's possible that the request is partially applied. In this case, it's unpredictable which parameters were applied and which not. Use the device_info command to confirm the new values.


This command can set most fields which appear in device_info. Name, location, JSON data type, and meaning are exactly the same as the fields in device_info.

If the client needs to change multiple parameters, it's recommended to set all parameters at once, using a single "set_params" command.


This sets the link offset to 64 samples and leaves all other parameters untouched:

User => Device
{"command": "set_params", "stream": { "link_offset": 64 }}
Device reply
{"seq": 0}

This sets multiple fields (set first and second channel sources, set link offset to 48 samples, set memo to "hello"), and leaves all other parameters untouched:

User => Device
{"command": "set_params", "stream": { "output_channels": [ 0, 1 ], "link_offset": 48 },
"ui": { "memo": "hello" } }
Device reply
{"seq": 0}


Show information about current AES67 streaming (network to device).





IP address of source stream.

portIntegerUDP port of source stream.
clock_offsetIntegerSDP mediaclk parameter (0 if unset)
link_offsetIntegerOffset chosen with set_session command.
samplesizeIntegerAudio sample size in bits per channel
samplerateIntegerAudio sample rate
channelsIntegerNumber of channels sent by source stream.
output_channelsArrayChannels used, as chosen with set_session command.
packet_dropsIntegerNew packet drops since last command.
packet_drop_last_msIntegerAbsolute device time of last packet drop in ms. Can wrap around.
rtp_received_last_msIntegerAbsolute device time of last RTP audio packet received. Can wrap around.
clock_lockedIntegerComplete lock. (device_info rtp.lock has more detailed information.)


This shows the status of AES67 streaming, as well as some of the selected parameters.

This command is for debugging, and you should probably not rely on it being present or compatible in the next firmware version.


User => Device
{"command": "show_rtp_status"}
Device reply
  "seq": 0,
  "ip": "",
  "port": 5004,
  "clock_offset": -1479386051,
  "link_offset": 64,
  "samplesize": 24,
  "samplerate": 48000,
  "channels": 1,
  "output_channels": [
  "packet_drops": 2147483647,
  "packet_drop_last_ms": 693922,
  "rtp_received_last_ms": 823747,
  "clock_locked": true,
  "ptp_sync_last_ms": 823543


Remove sessions from the device's session cache.


ageFloatMaximum session age in seconds that should be preserved (older sessions
are deleted). (Default: 60)
blocktimeFloatTime in seconds during which new SAPs should be ignored. (Default: 0)


Success/error only.


The device caches previously discovered sessions (using the SAP protocol) and hold them in RAM and on the flash. There is currently a session timeout of 100 seconds, after which a session is automatically deleted. This command can be used to remove a session immediately. Sometimes, it can help resolving playback problems by removing broken cache entries.

The "blocktime" parameter can be set if sap_purge is supposed to be run on all devices in the network. The problem is that the devices will never execute the command at exactly the same time, and bogus SAP packets still could be propagating through the network. In particular, devices will send their own bogus cached/replicated SAPs to all other devices. To avoid that devices immediately readd bogus sessions, this parameter can disable SAP processing temporarily.


Wipe all settings.






This resets all user settings, and reboots. Like with the "reboot" command, no response is sent. The device_id is not changed.


Reboots the device.






The device is reset, as if power-cycled. There is no response, because the hardware is reset before a response can be sent out.


User => Device
{"command": "reboot"}


Document version: 32 / Mär 07, 2023 15:08

Download this document as PDF