Skip to content
apeters1971Public

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

AES67

AES67 for Apple Silicon

Experimental / untested. This project is early and not a finished product. Do not use it in a show, broadcast, or any situation where audio must be reliable. Timing, multicast, PTP, and interoperability have not been systematically verified.

A bidirectional AES67 audio interface for macOS on Apple Silicon. Apps see a Core Audio device named AES67. Playback is packetized as AES67/RTP and sent to the network; received AES67/RTP is presented as the device’s inputs.

This is a userspace stack — the supported way to ship a virtual audio device on modern macOS. Kernel extensions are not used.

DAW / system audio
        │
        ▼
 AES67.driver   ← Audio Server Plug-in (HAL)
        │  shared ring (/Users/Shared/aes67.audio.ring)
        ▼
     aes67d     ← RTP + SDP + SAP + PTPv2 follower or software master
        │
        ▼
  multicast LAN (AES67)

What you get

Path Role
AES67.driver Duplex Core Audio device (default 8 in / 8 out, 48 kHz, 32-bit float)
aes67d Real-time engine: RTP send/receive, SAP announce, software PTP
aes67ctl Status, SDP export, SDP import, 1 kHz test tone
AES67 Panel.app Live GUI: channels, buffers, TX/RX, PTP, HAL status
Web display Localhost page at http://127.0.0.1:8765/ — same tabs and routing/jitter/SDP controls as the Mac panel (Dante ping, JITT, LAT, firewall, and link stay native)

Wire format follows AES67 interoperability practice:

  • RTP/AVP, payload L24 (or L16)
  • 48 kHz, 1 ms packet time (48 frames)
  • Multicast (default TX 239.69.83.1:5004, RX 239.69.84.1:5004) or unicast
  • SDP with rtpmap, ptime, ts-refclk / mediaclk (RFC 7273)
  • SAP announcements on 224.2.127.254:9875
  • PTPv2 follower on domain 0 (224.0.1.129), or software two-step ordinary clock master (Announce + Sync + Follow_Up)

Build

Requires Xcode CLT and CMake (Apple Silicon).

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure

Binaries land in build/: aes67d, aes67ctl, AES67.driver, AES67 Panel.app, plus unit tests.

Install

Installer package

After a Release build, make a double-clickable product archive (.pkg) and a disk image (.dmg):

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --build build --target installer
open build/dist/AES67-1.0.0.dmg

Or ./scripts/make_installer.sh. The package installs the HAL plug-in, aes67d / aes67ctl, LaunchDaemon com.aes67.daemon, a default config if none exists, and AES67 Panel in /Applications. It asks for an administrator password, then restarts coreaudiod.

The .pkg is unsigned. Gatekeeper will warn until you sign and notarize it with your Apple Developer ID (productsign, notarytool). Apple Silicon (arm64) and macOS 13+ only.

Uninstall from Terminal: sudo /usr/local/share/aes67/uninstall.sh (leaves aes67.conf).

Script install

./scripts/install.sh

That is the same payload as the package, without the Installer.app UI.

Uninstall:

./scripts/uninstall.sh

Use

  1. Open AES67 Panel (open build/AES67\ Panel.app or /Applications/AES67 Panel.app after install). Set routing, then use a DAW or system audio. See AES67 Panel and Web display below.
  2. Open Audio MIDI Setup and select AES67 (64 channels in and out).
  3. Point a DAW output at AES67 — that audio is sent as an AES67 stream.
  4. Subscribe this Mac to a remote stream by importing its SDP:
aes67ctl status
aes67ctl sdp > /tmp/this-mac.sdp
aes67ctl import /path/to/remote.sdp
aes67ctl apply '{"channels":8,"tx_address":"239.69.83.1","tx_port":5004,"rx_address":"239.69.83.2","rx_port":5004}'

apply / Apply routing rebinds TX and RX immediately and writes the config file. Changing channel count also needs a Core Audio reload (sudo killall coreaudiod) so the HAL device matches.

Bring up a stream between two Macs

The receiver locks next_seq to the first packet it sees, then play-out runs on a 1 ms clock. If that cursor gets ahead of the wire (jitter, both sides starting at once, a burst into a small buffer), lost climbs, the RX lamp goes red, and it will not re-lock by itself. Disable RX on both Macs, wait until TX is running, then enable RX. That re-primes both receivers against live packets.

  1. On Routing, set addresses (unicast or crossed multicast; 8 ch for Mac-to-Mac). Apply on both. Do not use WING.
  2. Click RX off on both Macs (lamp grey). Leave TX on on both (green).
  3. Confirm each side is sending (~1000 pkt/s on Transmit). Optional: 1 kHz tone on the sender.
  4. Click RX on on both Macs. The lamp should stay green. Meters on In should move on the listener.
  5. If RX goes red, lost is increasing. Click RX off on that Mac (or both), then on again. Lifetime lost/late counts do not reset; the lamp watches whether lost is still climbing.

Simplex (TX on one Mac, RX on the other) is the easy first check. Duplex is the same bring-up with TX already on both sides before you enable RX.

aes67ctl rx off / aes67ctl rx on is the same re-prime if you are not using the panel.

System audio (channels 1–2)

macOS stereo (Music, Safari, YouTube, system alerts) uses only the first two Core Audio channels of a multi-channel device. If you set AES67 as the Mac’s sound output, that mix is AES67 channel 1 (left) and channel 2 (right) on the first flow. Channels 3–64 stay silent unless a DAW or other app writes them.

  1. Install the driver and keep aes67d running (TX enabled) with routing applied. After installing a new driver, sudo killall coreaudiod so the HAL reloads. Test tone / test mode do not use this path — they are generated inside aes67d.
  2. In Audio MIDI Setup, confirm AES67 is 64 in / 64 out at 48 kHz.
  3. In System Settings → Sound → Output, choose AES67. Optionally set Input to AES67 as well (default input is then receive 1–2).
  4. Play music as usual. The AES67 listener must subscribe to this Mac’s transmit (same TX address/port or this Mac’s TX SDP). They will hear stereo on their channels 1+2.

Your Mac speakers will go quiet unless you build an Aggregate Device (Built-in + AES67) or listen on the remote. Logic Pro and other DAWs can still address all 64 channels when they use AES67 as their audio device; system-wide Sound settings do not.

Behringer WING Dante

Dante AES67 on the WING card is 8 channels per multicast flow. 48 mixer channels are six 8-channel flows, not one L24/48000/48 stream. Use the panel WING button (or aes67ctl wing 192.168.1.40) with the Dante card IP. That keeps the 64×64 Core Audio device, maps ch 1–48 as six flows, uses L24 / 48 kHz / 1 ms, and transmits on 239.69.x.x … +5. RX addresses come from SAP if the WING has already announced its flows.

On the WING side, in Dante Controller: enable AES67, reboot the card, create six 8-channel AES67 multicast flows, then subscribe the WING to this Mac’s six TX SDPs. The panel 48 ch and 64 ch stream buttons also split into 8-channel Dante-legal flows (6 and 8 flows). 8 ch stays a single flow.

Send a 1 kHz alignment tone without a DAW:

aes67ctl tone on
aes67ctl tone off

Print the transmit SDP without starting I/O:

aes67d --print-sdp --config config/aes67.conf

AES67 Panel

AES67 Panel.app is the native control surface. It does not process audio. It talks to aes67d over the Unix socket /tmp/aes67d.sock (the same protocol as aes67ctl): it polls STATUSJSON several times a second and sends SET, WING, TX/RX/DRIFT ON|OFF, TONE, TEST, LEVEL, and SDP. Apply writes /Library/Application Support/AES67/aes67.conf. If the daemon is down, the header aes67d lamp is red and the other tabs show a waiting state.

Open /Applications/AES67 Panel.app after install, or open build/AES67\ Panel.app. A short splash (logo + “Created by Dr. J Music”) uses a square window; then the window sizes to the current tab. A menu-bar extra uses the same logo (Show / About / Quit).

Header lamps

These stay visible on every tab. Green / amber / red / grey are status. TX, RX, DRIFT, and FW are also controls (the title bar is clickable so a tap does not drag the window).

Lamp Meaning
LAN / WIFI5–7 Path the daemon is using. Red if there is no network.
FW macOS Application Firewall vs aes67d and a UDP send to the RTP port (also SAP 9875, PTP 319/320). Hover for the reason. Click opens System Settings → Firewall. This is not the router firewall.
JITT Last 1 s Dante-ping jitter vs the jitter buffer: green if there is enough headroom, amber if tight, red if jitter exceeds the buffer, grey if there is no ping yet.
TX / RX Stream enable. Click to toggle (TX ON/OFF, RX ON/OFF). Grey = off. RX is red while lost is increasing (play-out cursor ran ahead). Toggle RX off and on to re-prime.
DRIFT Receive clock drift correct. Click to toggle. Grey = off.
PTP Green if this Mac is the software clock master, or if the follower is locked. Red if unlocked (host clock). Click to start or stop announcing. One Mac only.
aes67d Daemon socket up (green) or down (red).
LAT Estimated one-way audio: ping average/2 + current jitter + jitter buffer + packet time + device latency. Needs a ping target (see Dante IP).

Tabs

LEVELS (default) — 64-channel peak meters for Out (DAW → net) and In (net → DAW). Test mode sweeps sines on all mapped channels; the Tone slider sets the test/tone peak (−60…0 dB, default −14). Dante ping ICMP-pings the IPv4 in the Routing Dante IP field once per second (60 s sparkline: min/max RTT, current jitter, jitter max). That IP is only a ping/SAP target, not the RTP destination.

ROUTING — Stream width 8 / 48 / 64 (Core Audio stays 64×64; 48 and 64 become six or eight 8-channel flows). TX and RX address + port: 239.x is multicast; a host IPv4 is unicast. Apply (⌘↩) sends SET and rebinds immediately. WING + Dante card IP applies the 48 kHz / L24 / 1 ms / 48 ch / 239.69.{3rd}.{4th} preset and fills RX from SAP if the card has announced. Do not use WING for Mac-to-Mac.

DEVICE & BUFFER — HAL / shm / test LEDs, device name, rates, local address. AUTO and 1 / 2 / 4 / 8 / 16 / 64 / 256 ms set the RX jitter buffer. AUTO is one-shot: max ping jitter + 2 ms, snapped up to the next step (so the JITT lamp can go green). Playback/capture occupancy is the HAL ring, not AES67 packetization.

TRANSMIT / RECEIVE — Per-side enable, running, destination, session, packet rate, HAL buffer fill. RX also shows lost / late / last seq.

CLOCK & CONTROL — PTP lock or software master, domain, offset, drift ppm and insert/drop counts. Become master announces PTPv2 so the other Mac can lock. Enable/Disable drift. Send 1 kHz tone, Copy TX SDP, and Listen: OFF or an incoming stereo pair (1+2 … 63+64) played on the Mac’s hardware output (Built-in / headphones — never the AES67 device, so it does not loop back into TX). The Panel plays from the capture shared memory; aes67ctl monitor 1+2 / POST /api/monitor set the same pair.

Web display

aes67d also serves a localhost page at http://127.0.0.1:8765/ (override with --http-port N, disable with --no-http). It binds 127.0.0.1 only. Do not expose it on the LAN: the LaunchDaemon runs as root and the page can change routing and enable TX/RX.

This is a second control surface for the same aes67d and aes67.conf. Tabs match the Mac panel: LEVELS, ROUTING, DEVICE & BUFFER, TRANSMIT / RECEIVE, CLOCK & CONTROL. Apply, WING, jitter presets (including 64 ms and 256 ms), tone level, test mode, Monitoring, and Copy TX SDP work here. Dante ping, JITT, LAT, the firewall lamp, and the network-link lamp stay in AES67 Panel (those need local ICMP and OS APIs). Web AUTO jitter is a fixed 8 ms step; ping-based AUTO is Mac-only. Hearing the monitor pair requires AES67 Panel.

The page polls GET /api/status (~150 ms) for the same JSON as STATUSJSON. Controls POST to /api/set (routing / jitter JSON), /api/wing, /api/level, /api/monitor, /api/sdp (GET), /api/ptp/master/on|off, and the existing /api/tx|rx|drift|tone|test/on|off toggles. Monitoring audio is played by AES67 Panel (user session), not by the web page.

After sudo ./scripts/install.sh, restart is enough for an existing browser tab; use a private window if the old page is cached.

Wi-Fi 7 (two Macs)

You can install this stack on two Apple Silicon Macs and send AES67 between them over a modern Wi-Fi 7 router. This is experimental. Wi-Fi is still a contention medium: multicast is often filtered, and jitter will move. One Mac can announce a software PTP clock; it is not a hardware grandmaster. Treat it as a laptop-to-laptop trial, not a show network.

Do not use the WING button. That is for a Dante card. On Routing, pick 8 ch (one flow). 48 ch / 64 ch split into consecutive addresses and are a poor fit for a single host, especially in unicast.

Both Macs: same SSID, no client isolation, no guest network. Allow Local Network for the panel. Keep aes67d installed as the LaunchDaemon. Put the other Mac’s LAN IP in Dante IP if you want ping, the JITTER lamp, and the LAT readout (that field is just an ICMP target; it is not the RTP path).

Unicast (usually more reliable on Wi-Fi)

TX is the other Mac’s IPv4. RX is this Mac’s IPv4. Same UDP port, usually 5004.

TX RX
Mac A (192.168.1.10) 192.168.1.20 192.168.1.10
Mac B (192.168.1.20) 192.168.1.10 192.168.1.20

Apply, then use Bring up a stream between two Macs above: RX off on both, TX on, then RX on. Play into AES67 on one Mac and record from AES67 on the other.

Multicast (needs router support)

Cross two groups so each Mac receives the other:

TX RX
Mac A 239.69.83.1:5004 239.69.84.1:5004
Mac B 239.69.84.1:5004 239.69.83.1:5004

Apply, then the same RX off → TX on → RX on bring-up as unicast.

Enable Wireless Multicast Forwarding (or multicast-to-unicast) and IGMP snooping/proxy. Some routers only forward LAN → Wi-Fi, not Wi-Fi client → Wi-Fi client. If packets never arrive, use unicast. With WMF, each listener may get its own unicast copy, so airtime grows with the number of receivers.

Latency and drift

The panel LAT estimate is one-way:

ping average / 2 + current jitter + jitter buffer + packet time + device latency

A modern Wi-Fi 7 hop between two Macs on the same AP is typically 1–2 ms. That is the network part only. Heard AES67 delay is still that plus the jitter buffer, 1 ms packet time, and ~1 ms device latency. With a 2 ms buffer (AUTO when ping jitter is low) one-way LAT is often about 5–8 ms; an 8 ms buffer lands around 11–14 ms. Round-trip audio is roughly twice that. Busy air, power save, or a mesh hop can push jitter to the 16–64 ms steps, or 256 ms. The JITTER lamp compares the last 1 s of ping jitter to the buffer.

On two Macs, make one of them the software PTP master (CLOCK & CONTROL → Become master, or aes67ctl ptp master on). That Mac sends Announce (1 Hz) and two-step Sync / Follow_Up (8 Hz) on 224.0.1.129. The other Mac stays a follower; its PTP lamp should go green when it hears Sync. Do not enable master on both. This is still software-timestamped (no NIC hardware timestamps) and uses the host monotonic clock, not a GPS/TAI grandmaster. On Wi-Fi, multicast PTP is often delayed or filtered; wired Ethernet is the better trial. Leave DRIFT on as a backup. Receive drift correct compares played RTP timestamps to the play-out clock over a 4 s sliding window (ready after 2 s), low-pass filters ppm, and clamps the applied rate to ±200 ppm. A slow occupancy trim keeps the jitter buffer near half full. It inserts or drops one sample (with interpolation). Packets are not marked late for sequence lead that came from a recent drop. It does not replace a hardware grandmaster and it does not remove Wi-Fi jitter.

Configuration

Edit /Library/Application Support/AES67/aes67.conf (a copy of config/aes67.conf):

sample_rate=48000
channels=8
encoding=L24
ptime_us=1000
interface=en0
tx.multicast=239.69.83.1
tx.port=5004
rx.multicast=239.69.84.1
rx.port=5004
ptp.domain=0
ptp.master=false

The Core Audio device is 64 in / 64 out. stream_channels is the total mapped AES67 channel count. Values of 16, 24, 32, 48, or 64 are sent as consecutive 8-channel Dante-style flows (tx.multicast, tx.multicast+1, …). Extra device channels stay local. After updating the plug-in, reinstall and sudo killall coreaudiod so apps pick up 64 channels.

tx.multicast / rx.multicast also accept a unicast address (AES67 allows both). Unicast is useful for a laptop-to-laptop link or a local loopback test (127.0.0.1).

The switch must forward multicast and IGMP. Many AES67 deployments also need PTP (boundary/transparent clock). Wired Ethernet is the correct AES67 medium. A Wi-Fi 7 router can be used for a two-Mac trial; see Wi-Fi 7 (two Macs) above.

macOS Sequoia and later gate LAN multicast behind Local Network permission. A user-launched unsigned binary may get sendto: No route to host. The LaunchDaemon installed by scripts/install.sh runs as root and is the supported way to send multicast. You can also set interface=en0 explicitly.

Limits (honest)

  • PTP is software-timestamped. macOS does not expose NIC hardware timestamps on typical Apple Silicon machines. A Mac set to master can announce a clock the other Mac’s follower can lock to; neither will match a hardware grandmaster the way a dedicated AES67 interface does. RTP timestamps still advance from the media clock estimate when locked (or from the host clock when this Mac is master); otherwise they use the host clock.
  • This is not Dante, RAVENNA-complete, ST 2110-30, or NMOS. It speaks the AES67 media/SDP profile used by many consoles and virtual sound cards.
  • The HAL plug-in is unsigned. Local install works; distributing it requires your own signing and notarization.
  • Channel count is fixed at plug-in load time. Do not change it while I/O is running.

License

GNU GPL v3 or later (GPL-3.0-or-later). See LICENSE.

You may use, modify, and distribute this program under GPLv3 or any later version published by the Free Software Foundation. If you distribute a binary (including a closed product that includes this code), you must also provide the corresponding source under the same license. You may charge for the program; you may not relicense the GPLv3+ parts as MIT or as proprietary-only.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages