https://d226lax1qjow5r.cloudfront.net/blog/blogposts/streaming-calls-to-a-browser-with-voice-websockets-dr/audio-websockets.png

Streaming von Anrufen an einen Browser mit Voice WebSockets

Zuletzt aktualisiert am November 5, 2020

Lesedauer: 5 Minuten

Wir haben kürzlich angekündigt WebSocket-Unterstützung in unserer neuen Voice API angekündigt. Die ersten Anwendungsfälle dafür betreffen die Server-zu-Server-Kommunikation zwischen der Vonage Voice API und Sprach-KI-Plattformen wie IBM Watson oder Amazon Alexa. Ich möchte Ihnen jedoch eine kleine Demo zeigen, die wir für unseren Stand bei AWS ReInvent erstellt haben und die eine andere Verwendung zeigt. Sie demonstriert das Streaming des Audios einer Telefonkonferenz an einen Webbrowser und gibt es mit der Web-Audio-API.

Warum sollten Sie das tun? Nun, zunächst einmal ist es eine schöne Möglichkeit, die neue WebSocket-Funktion zu zeigen, aber als ich mit der Entwicklung begann, wurde mir klar, dass es perfekt für Anwendungsfälle ist, in denen man eine große Anzahl von Personen im "Nur-Hören"-Modus für einen Anruf haben möchte. Nehmen Sie die typischen "All-Hands"-Telefonkonferenzen großer Unternehmen. Wenn mehr als 5-10 Personen miteinander sprechen, entsteht ein Chaos. Bei diesen groß angelegten Anrufen sind die meisten Teilnehmer nur passive Zuhörer, die (hoffentlich) stumm bleiben. Aber das ist ziemlich ineffizient und kostspielig. Es ist auch nicht besonders gut skalierbar. Diese regelmäßigen Teilnehmer sind nicht wirklich in eine Telefonkonferenz involviert; ihre Teilnahme ist eher so, als würden sie einem Talk-Radiosender zuhören. Warum sollte man sie also nicht mit einer rundfunkähnlichen Technologie einbinden?

Die neue Vonage WebSocket Voice API ist eine großartige Lösung für diesen Anwendungsfall. Ich zeige Ihnen, wie Sie eine App erstellen, die den Ton eines Anrufs an eine Reihe von verbundenen Browsern überträgt.

Die technischen Details

Ich werde Ihnen die technischen Details dieser Funktion erläutern und Ihnen hoffentlich ein Verständnis für unsere Voice WebSocket-Funktionen vermitteln. Ich bin wirklich begeistert von dieser Funktion, denn sie eröffnet eine ganze Welt von Möglichkeiten für die Integration von Voice in Web Applications.

Hier ist ein Diagramm, das zeigt, wie das alles zusammenpasst:

architecturearchitecture

Die Telefonkonferenz

Wir haben zwei "Domains": Eine typische Telefonkonferenz, die über die Vonage Voice APIgehostet wird, in die sich die Gesprächspartner einwählen. Der Code dafür ist recht einfach, denn wir müssen nur eine neue Vonage-Anwendung erstellen und die answer_url auf einen NCCO verweisen, der die Konferenz erstellt. Diese wird vom Web-App-Server aus bedient.

Der NCCO sieht so aus:

[
{
"action": "talk",
"text": "Connecting to Audio Socket Conf"
},
{
"action": "conversation",
"name": "audiosocket",
"eventUrl" : ["http://example.com/event"]
}
]

Wenn Benutzer also eine mit der Anwendung verknüpfte Nummer anrufen, werden sie in eine sehr einfache Konferenz geschaltet. Darüber hinaus senden wir Ereignisse über den Status der Telefonkonferenz an den Web-App-Server. Sie können hier natürlich auch erweiterte Funktionen wie Moderation, eine PIN usw. hinzufügen.

Ein WebSocket-Teilnehmer

Jetzt kommt der (etwas) komplizierte Teil. Wenn Sie mit der Vonage WebSocket-API interagieren, ist Ihre Anwendung kein WebSocket-Client (z. B. ein Browser). Ihre Anwendung ist ein WebSocket-Server. Ihr Anwendungsserver muss also eine Anfrage an die Vonage REST API stellen, um der Voice-Plattform mitzuteilen, dass sie Ihre Anwendung zu einem Teilnehmer der Konferenz machen soll, indem sie eine ausgehende WebSocket-Verbindung zu Ihrem Web-App-Server herstellt. Um dies zu tun, zeigen wir die answer_url des ausgehenden Anrufs auf denselben NCCO, den wir für die Telefonanrufe verwendet haben.

Die Anfrage für einen ausgehenden Anruf an den Websocket sieht wie folgt aus:

POST /v1/calls
Host: api.nexmo.com
Authorization: Bearer [YOUR_JWT_TOKEN]

{ "to": [{
"type": "websocket",
"uri": "ws://example.com/socket",
"content-type": "audio/l16;rate=16000",
"headers": {
"app": "audiosocket"
}
}],
"from": {
"type": "phone",
"number": "442037831800"
},
"answer_url": ["http://example.com/ncco"]
}

Dieses Sequenzdiagramm zeigt die Abläufe:

sequence diagramsequence diagram

Um sicherzustellen, dass nur eine Websocket-Verbindung von Vonage zum App-Server aufgebaut wird, müssen Sie in Ihrer Anwendung den Status dieses Aufrufs und seine Aufrufkennung (callid). Ich überprüfe die Anzahl der bestehenden Client-Verbindungen. Wenn sie Null ist, schließe ich den Websocket-Aufruf wieder über die REST-API. Nur die erste Client-Verbindung initiiert die Verbindung von Vonage.

Behandlung eingehender WebSocket-Daten

Sobald die Websocket-Verbindung zwischen Vonage und dem App-Server hergestellt ist, müssen wir verstehen, was sie sendet. Bei der ersten Verbindung sendet die Voice API eine einzelne Text-"Nachricht", die einige JSON-Daten enthält. Dabei handelt es sich hauptsächlich um eine Beschreibung des Audioformats zusammen mit zusätzlichen Werten, die Sie von Ihrer Anwendung über den NCCO beim Aufbau der Verbindung übergeben (in diesem Fall app : audiosocket).

{
"app": "audiosocket",
"content-type": "audio/l16;rate=16000"
}

Nach der anfänglichen Textnachricht sendet Vonage dann binäre Nachrichten, die jeweils 20 ms RAW-Audio enthalten. (Hinweis: RAW-Audio ist nicht ganz dasselbe wie eine .wav-Datei.) Das bedeutet, dass Sie in Ihrem Code feststellen müssen, ob es sich bei der empfangenen Nachricht um eine Text- oder Binärnachricht handelt, und diese entsprechend behandeln müssen.

Senden von Audiodaten an den Browser

Um diese Audiodaten in einem Browser abspielen zu können WebAudioabzuspielen, müssen wir das RAW-Audio in eine .wav-Datei umwandeln. Das bedeutet, dass ein kleiner 44-Byte-Header zur Datei hinzugefügt wird. Dies für jeden 20ms-Frame zu tun, wäre jedoch ein ziemlicher Overhead, und angesichts unseres Anwendungsfalls können wir eine kleine Latenz gegenüber den Zuhörern tolerieren. Um dies zu vermeiden, können wir 10 der Nachrichten von Vonage puffern, sie zusammenfügen und den 44-Byte-Header an den Anfang setzen. So erhalten wir eine 200 ms lange .wav-Datei.

Anschließend können wir diese .wav-Dateien an die Clients übertragen, indem wir eine Liste der verbundenen Client-Websockets durchgehen und jedem die Datei als Binärnachricht senden.

Abspielen von Audio in einem Browser

Auf dem Webclient müssen wir ein JavaScript erstellen, um eine Verbindung zum Websocket-Server herzustellen und dann die empfangenen Audionachrichten zu verarbeiten. Da das Audioformat, das wir von Vonage erhalten, 16bit 16Khz ist und das native Format der meisten Browser 32bit 44.1Khz ist, können wir mit WebAudio keinen konstanten Stream wiedergeben. Wir müssen den Browser auffordern, die Audiodaten in die entsprechende Wiedergaberate zu transkodieren. Die WebAudio bufferSource kann dies sehr gut und mit sehr geringer Latenz, aber sie kann nur mit diskreten Dateien arbeiten und für jede Datei muss eine neue Instanz erstellt werden. Wenn also eine neue Audiodatei auf dem Websocket ankommt, müssen wir sie an eine Funktion übergeben, die eine neue bufferSource erstellt und sie auf dem Main audioContext.

Ein weiterer zu berücksichtigender Punkt ist das Timing. Die Umstellung auf weniger, aber dafür längere Samples (200 ms gegenüber 20 ms) hilft zwar bei der Verringerung des Jitters, aber die Nachrichten kommen immer noch nicht in genau dem richtigen Intervall an. Wenn wir sie also einfach nacheinander abspielen, wird es zu Störungen kommen. Glücklicherweise verfügt WebAudio über eine sehr präzise Timing-Schnittstelle, die helfen kann. Man nimmt die Zeit des ersten Samples als T0 nehmen, dann die Anzahl der empfangenen Nachrichten zählen und diese mit 0.2multipliziert, können wir jedes Sample so planen, dass es zum richtigen Zeitpunkt gestartet wird, und den Stream wieder so zusammensetzen, dass er praktisch störungsfrei ist.

Diese Teile des Client-Codes werden im Folgenden mit Kommentaren erläutert:

var startTime; // Make startTime a global var

ws.onmessage = function(event) {
// On the first message set the startTime to the currentTime from the audio context
if (count ==0){
startTime = audioContext.currentTime;
}

audioContext.decodeAudioData(event.data, function(data) {
count ++; // Keep a count of how many messages have been received
var playTime = startTime + (count *0.2) //Play each at file 200ms
playSound(data, playTime); //call the function to play the sample at the appropriate time
});
};

function playSound(buffer, playTime) {
var source = audioContext.createBufferSource(); //Create a new BufferSource fr the
source.buffer = buffer; // Put the sample content into the buffer
source.start(playTime); // Set the starting time of the sample to the scheduled play time
source.connect(analyserNode); //Connect the source to the visualiser
source.connect(audioContext.destination); // Also Connect the source to the audio output
}

Natürlich gibt es immer noch das Szenario, dass eine Datei zu spät zu ihrem geplanten Startzeitpunkt eintrifft. WebAudio ist in diesem Fall jedoch sehr clever und passt die Wiedergabe so an, dass sie an der richtigen Stelle beginnt, so als ob sie von Anfang an da gewesen wäre. Wenn also ein 200ms Sample abgespielt werden soll bei T 1200ms abgespielt werden soll, das aber nicht vor 1300msaufgerufen wird, springt WebAudio 100ms in das Sample, um mit der Wiedergabe zu beginnen. Das bedeutet, dass es gelegentlich zu Störungen kommen kann, wenn kleine Starts von Samples verpasst werden, aber das ist für Audio im Stil eines Telefonats durchaus akzeptabel. Bei qualitativ hochwertiger Musik funktioniert es vielleicht nicht so gut.

Holen Sie sich den Code

Und schon haben Sie einen Einweg-Audiostream mit geringer Latenz, der direkt im Browser wiedergegeben wird.

Schauen Sie sich den Code auf der Vonage Gemeinschaft GitHub Organisation und erfahren Sie mehr über die Vonage WebSocket Voice API in den Dokumentationen.

Teilen Sie:

https://a.storyblok.com/f/270183/384x384/7fbbc7293b/sammachin.png
Sam MachinVonage Ehemalige