AES67 auf dem 35C3


Auf dem 35C3 hat der Audio-Zweig des C3VOC dieses Jahr zum ersten mal intensiver mit AE67 experimentiert. AES67 ist quasi die standardisierte Variante von Audinate's Dante: Ein Echtzeit-Audio-Netzwerk auf IP-Basis.

AES67 besteht grundsätzlich aus drei Netwerkprotokollen:

Für viele Zwecke genügt es, nur die Audio-Daten via RTP zu empfangen. Wenn die Verarbeitung nicht in Echtzeit erfolgen soll ist eine synchrone Clock nicht unbedingt erforderlich. Ohne SDP müssen allerdings alle IP-Adressen und Kanalnamen manuell konfiguriert werden.

Konfiguration

Auf dem 35C3 kamen in den Sälen Stagetec Nexus als Audio-Routing und -Mixing-Systeme zum Einsatz – je ein Chassis je Raum. Dort wurden alle Audioquellen des Saals (Mikrofone, Zuspieler, Übersetzerkabinen) als analoge Signale engegengenommen und in ein hybrides Dante/AES67-Netz eingespeist.

Konfiguration der AES67-Flows für Saal Adams auf dem 35C3

Stereopaare und Mikrofone, die eng beieinander stehen, wurden dabei im selben Flow konfiguriert. Alle Kanäle eines Flows werden gemeinsam in UDP/RTP-Paketen auf einer Multicast-IP-Adresse verschickt und bleiben daher garantiert zueinander im Sync. Pakete aus verschiedenen Flows werden in separaten UDP-Paketen verschickt und können zeitlich unabhängig voneinander eintreffen oder auch unabhängig voneinander verloren gehen.

Empfangen

Um einen solchen AES67-Flow unter Linux zu empfangen braucht es eigentlich nicht viel. Alle nötigen Komponenten bringt GStreamer bereits mit. Eine einfache Beispiel-Commandline schaut wie folgt aus:

gst-launch udpsrc address=239.23.42.1 port=5005 multicast-iface=eth0 !\
    application/x-rtp, clock-rate=48000, channels=8 !\
    rtpL24depay !\
    audio/x-raw, format=S24BE, channels=8, rate=48000 !\
    alsasink sync=false

Diese Pipeline ist denkbar einfach: Alles was auf der benannten UDP Multicast-Gruppe ankommt wird dem RTP ausgepackt und ausgegeben, so wie es eben ankommt – ohne Rücksicht auf Zeitstempel oder eventuelles Reordering der UDP Pakete.

Ähnlich einfach sieht eine Basis-Pipeline zum Aufzeichnen aus. Hier muss nur noch die Endianess der Samples für .wav angepasst werden:

gst-launch udpsrc address=239.23.42.1 port=5005 multicast-iface=eth0 !\
    application/x-rtp, clock-rate=48000, channels=8 !\
    rtpL24depay !\
    audio/x-raw, format=S24BE, channels=8, rate=48000 !\
    audioconvert !\
    audio/x-raw, format=S24LE, channels=8, rate=48000 !\
    wavenc !\
    filesink location=/tmp/foo-wav

Clocking und Timing

Diese Pipelines ignorieren jegliches Timing der Pakete – alles wird eben so verarbeitet wie es ankommt. Damit kommt man schon erstaunlich weit, wenn man Audiodaten nur empfangen und nicht synchron weiterverarbeiten will.

Zur korrektur von Netzwerk-Jitter und UDP-Paket reordering sieht GStreamer das rtpjitterbuffer-Element vor. Dieses verwendet die im RTP-Strom üblicherweise vorhandenen Zeitstempel um die empfangenen Pakete zu ordnen und Lücken zu erkennen.

Möchte man Audiodaten von verschiedenen Quellen verarbeiten, synchron an mehreren Stellen ausgeben oder verarbeitetes Audio ins AES67-Netz zürückspeisen, ist es erforderlich die eigene GStreamer-Pipeline zur globalen PTP-Clock zu syncen. GStreamer bringt PTP-Unterstützung mit.

Probleme mit dem Clocking auf dem 35C3

Auf dem 35C3 waren wir jedoch nicht in der Lage, einen Clock-Sync zu den verwendeten Dante/AES67-Karten aufzubauen. Möglicherweise war hier eine fehlerhafte Switch-Config (QOS, IGMP-Snooping) ursächlich, genauer konnten wir das aber während der Veranstaltung nicht mehr analysieren.

Hierfür spricht auch, dass in den aus dem AES67 aufgezeichneten .wav-Dateien Samples fehlen (Hörbeispiel – zunächst das Audio mit dem fehlenden Segment und unabhäng davon aufgetretenen Mikrofon-Phasing, dann der korrekte Ton).

Die auf dem 35C3 eingesetzten Dante/AES67-Karten von Audinate schienen ebenfalls nicht mit rtpjitterbuffer kompatibel zu sein, weswegen wir auf dieses Element verzichtet und die "triviale" Version der Pipelines benutzt haben, wie sie oben beispielhaft abgebildet sind. Leider war auch hier die Zeit zu knapp dieses Problem genauer zu analysieren.

Dadurch haben wir erst nach dem Congress festgestellt, dass in den sonst sehr stabilen Aufzeichnungen hin und wieder Samples fehlen fehlten.

SAP/Discovery

Für den 35C3 haben wir uns gegen Auto-Discovery entschieden und alle Parameter manuell Konfiguriert. In der Praxis waren das aber ziemlich viele bewegliche Teile (~25 Multicast-IP-Adressen, ~160 Kanalnamen) so dass es hier trotz sorgfältiger Prüfung zu fehlern kam. Zukünfig wäre es wichtig, die Config zumindest automatisch generieren zu können.

AES67 Multitrack Recorder

Die erste der nbeiden AES67-Basierte Software-Projekte, die auf dem 35C3 im Einsatz war, ist der AES67 Multitrack Recorder.

Screenshot der WebGUI des AES67 Multitrack Recorders.

Im vergangenen Jahren hatten wir eine Teststellung von Tascam und 4x Tascam DA6400 mit Dante-Karte benutzt. Das lief zwar sehr stabil, hatte jedoch für unseren Einsatzzweck (4 weit verteilte Räume für 5 Tage fast permanentes Recording) einige gravierende Nachteile:

Menschen mussten regelmäßig SSDs wechseln, ins Büro tragen, auf ein Storage entleren und zurücktragen. Oft war unklar, wo genau das Backup zu einem bestimmten Vortrag denn grade war. Noch im Recorder? Grade in der Messe unterwegs? Schon auf dem Storage? Wenn ja in welchem Ordner genau? Manchmal fehlten morgens die SSDs in den Räumen und mussten erst noch geleert werden.

Dazu kamen einige konkrete Probleme wie nicht korrekt gestellte Uhren auf den Tascam-Recordern, keine sinnvolle Dateibenennung (was war Channel 46 noch mal?) und 4GB große wav-Files statt handlicher 15-Minuten stücke. Vieles davon hätte man vermutlich aber auch auf den Tascam-Recordern korrekt konfigurieren können.

Diese Erfahrungen im Hinterkopf habe ich den AES67 Recorder implementiert, der auf einem (optimalerweise) Linux-Server läuft und die einfließenden Audiodaten in Dateien ablegt, deren Name sowohl den echten Kanalnamen als auch das echte Datum enthält. Die Länge der Dateien ist konfigurierbar und können noch während der Aufnahme auf das Storage synchronisiert werden. So wussten wir zu jedem Zeitpunkt genau, wo die Audio-Backups sind.

Es gab einige kleinere Problem mit der (vom Recorder unabhängigen GUI) sowie die zuvor genannten Aussetzer. Dadurch passte die Laufzeit der Audiodateien nicht mehr zu den Videos und das Backup war als solches am Ende wenig brauchbar, jedoch hat das Konzept sehr gut funktioniert.

AES67 Loudness Monitor

Wir verwenden bereits seit einigen Jahren den ebur128 Loudness-Graphen von ffmpeg, um eine Konstante Lautheit unserer Streams und Recordings zu gewährleisten. Üblicherweise leiten wie diesen ganz am Ende der Kette, also bei den Livestreams ab. Für den 35C3 haben wir für die Audio-Regie zusätzlich einen Echtzeit-Loudness-Meter gebaut.

Multiview aus den Loudness-Graphen aller Säle

Da ffmpeg Probleme mit dem L24PCM in RTP hat, GStreamer aber keinen ebur128 Loudness-Meter hat, haben wir eine ziemlich abenteuerliche Kombination aus GStreamer, ffmpeg und ffplay benutzt:

gst-launch-1.0 -q udpsrc multicast-iface=eth0 address=239.23.42.2 port=5004 !\
    application/x-rtp, clock-rate=48000, channels=2 !\
    rtpL24depay !\
    audioconvert !\
    wavenc !\
    fdsink |\
        /opt/ffmpeg-4.1-64bit-static/ffmpeg -y -nostdin -i - -filter_complex '
            [0] ebur128=video=1:target=-16:gauge=shortterm:scale=relative [v][a]
        ' -map '[v]' -map '[a]' \
        -pix_fmt yuv420p -c:v rawvideo \
        -c:a pcm_s16le -f matroska - |\
            ffplay -v 0 -autoexit -exitonkeydown -exitonmousedown -window_title "ebur128 Loudness" -

Wie nicht anders zu erwarte, haben sich die Unix-Pipes zwischen den einzelnen Prozessen hin und wieder verklemmt, was aber durch regelmäßiges Neustarten der Prozesse behoben werden konnte. Für die Zukunft habe ich vor, einen ebur128 Loudness-Graphen als GStreamer-Element zu entwicklen und damit nicht nur diesen Eiunsatzzweck einfacher zu machen.