Repository navigation
Communication AOO
AoO - Audio over OSC is a message based real-time audio streaming protocol that uses Open Sound Control as its wire format: it sends audio from arbitrary senders (sources) to arbitrary receivers (sinks) over UDP. Unlike plain UDP the receiver knows the format, buffers the data as jitter buffer, requests lost packets again, compensates the clock drift and can mix several senders. It is the protocol of the official aoo library, which is also available for Pure Data and SuperCollider.
The implementation is provided by the separate arduino-aoo library, which is based on the AudioTools. See Communication for the general considerations.
cd ~/Documents/Arduino/libraries
git clone https://github.com/pschatzmann/arduino-audio-tools.git
git clone https://github.com/pschatzmann/arduino-aoo.gitFor Opus you also need the arduino-libopus library.
| Class | Description |
|---|---|
AOOSender |
an audio sink (AudioOutput): encodes (PCM by default, or any AudioTools encoder, e.g. Opus) and sends the audio; answers resend requests, pings, invitations |
AOOReceiver |
an audio source (AudioStream): receives, buffers and decodes the audio of one or several senders and mixes them; pull the audio with a StreamCopy
|
AOOReceiverTask |
same as AOOReceiver, but the messages are received in a separate task (ESP32: on the other core) |
AOOStreamUDP |
UDP transport (unicast or multicast) |
AOOStreamSerial |
serial transport with HDLC framing |
#include "AudioTools.h"
#include "AOO.h"
AudioInfo info(48000, 1, 16);
SineWaveGenerator<int16_t> sineWave;
GeneratedSoundStream<int16_t> sound(sineWave);
Throttle throttled(sound); // the generator is faster than real time
AOOStreamUDP udp(IPAddress(192, 168, 1, 44), 9999); // receiver
AOOSender sender(1, udp); // source id 1
StreamCopy copier(sender, throttled);
void setup() {
// connect to WiFi ...
sineWave.begin(info, N_B4);
throttled.begin(info);
auto cfg = sender.defaultConfig();
cfg.copyFrom(info);
// cfg.redundancy = 2; // send each block twice
sender.begin(cfg);
}
void loop() { copier.copy(); }#include "AudioTools.h"
#include "AOO.h"
AudioInfo info(48000, 2, 16); // output format: all senders are converted to it
AOOStreamUDP udp(9999);
AOOReceiver receiver(1, udp); // sink id 1
I2SStream i2s;
StreamCopy copier(i2s, receiver);
void setup() {
// connect to WiFi ...
auto cfg_i2s = i2s.defaultConfig(TX_MODE);
cfg_i2s.copyFrom(info);
i2s.begin(cfg_i2s);
auto cfg = receiver.defaultConfig();
cfg.copyFrom(info);
receiver.begin(cfg);
}
void loop() { copier.copy(); }To check the received data you can replace the I2SStream with a CsvOutput (paced with a Throttle): use a low sample rate on a microcontroller, because a CSV line per frame needs a lot of bandwidth.
Register the codec on both sides; like the official library one Opus stream per channel is used:
#include "AudioTools/AudioCodecs/CodecOpusMultiStream.h"
OpusMultiStreamAudioEncoder opus; // sender
opus.config().default_channel_mapping = OPUS_CHANNEL_MAPPING_SEPARATE;
opus.config().frame_sizes_ms_x2 = OPUS_FRAMESIZE_20_MS;
sender.setEncoder("opus", opus);
receiver.addDecoder("opus", []() { // receiver: a new decoder for each sender
auto* decoder = new OpusMultiStreamAudioDecoder();
decoder->config().default_channel_mapping = OPUS_CHANNEL_MAPPING_SEPARATE;
return (AudioDecoder*)decoder;
});On the ESP32 increase the loop stack size for Opus (e.g. SET_LOOP_TASK_STACK_SIZE(30000)) and use complexity = 1 on the sender.
The receiver primes its buffer (buffer_depth blocks) to half before it starts the playback and fills missing blocks with silence. Lost blocks are requested again (resend requests, default), and the sender can send each block several times (redundancy): resend requests cost almost no bandwidth and work well with Opus and 20 ms frames, redundancy is the most robust against random loss but multiplies the bandwidth. The clock drift is compensated by adaptive resampling. See Choosing the Error Correction for the measurements and recommendations.
Blocks which do not fit into max_packet_size (default 960 bytes) are split into several UDP packets: keep it below the MTU of your network, because the ESP32 drops fragmented packets.
The implementation has been tested in both directions against the official aoo library (PCM and Opus, on the desktop and with an ESP32-S3 over WiFi): e.g. you can receive the audio of a microcontroller in Pure Data or SuperCollider, or send audio from there to a microcontroller. Use the same ids and ports on both sides (the examples use sink id 1 on port 9999 and the source port 9998, like the official examples).
- Examples: sender, receiver, receiver with task
- Quick start and architecture with all configuration options
- Desktop build and tests
- AoO protocol specification