Hi
Ich brauche Unterstützung bei einer Websocket Verbindung welcher mit einer Bearer Token Authentifizierung geöffnet wird.
Nach dem öffnen des Sockets soll ein Audio Aufnahme gestartet werden welche als Stream gestartet werden.
Dieser Stream wird dann vom dem Webservice analysiert und als Text zurück gegebenen.
Die Authentifizierung sowie das öffnen des Sockets passt nur sobald ich versuche den Audio per Byte versuche hoch zu streamen passiert nix!
Auch eine „ws.close“ bringt dann nicht das korrekte Ergebnis.
Bin nich der gebohrene Entwickler und denk das ich ein Problem bei der asynchronen Ausführung einzelner voids habe.
Hat vielleicht jemand Erfahrung mit solchen Services und kann mir helfen?
Mit freundlichen Grüßen
Stephan
Es wäre hilfreich wenn du denn Code / einen Link zur repo posten könntest, damit man dir besser helfen kann.
Ohne konkreten Code oder eingrezung des Fehlers kann man nur raten wo das Problem sein könnte. Der Fehler muss ja nicht unbedingt beim hochladen per WebSocket sein sondern es könnte vllt. auch sein das es schon beim Aufnehmen nichts kommt.
lg Suiram1
Hi Suiram1
Natürlich...
hier ist ein Teil des Codes.
Den Websocket baue ich mit "StartWebSocket(..)" auf und erhalte nach der "SendWebSocketConfig()" eine Erfolgsmeldung des Server zurück.
Via eines Buttons wird die Funktion "StartAudioStreaming" welche das lokale Mikrofon aktivieren und den live Stream in 500ms Chunks über den "ws.Send(chunk)" an den Socket senden soll. Laut Aussage des Providers zeigt das Websocket Logfile, dass die Authentifizeung sowie die Übertragung der Config erfolgreich ist. Es kommen aber keine Sounddaten an.
Diese sollen in 500ms chunks als raw binary versand werden.
using RestSharp;
using WebSocketSharp;
using NAudio.Wave;
using Corti_Connect.Class;
using System.Text.Json;
namespace Speech_Connect
{
public partial class frmCCMain : Form
{
....
..
const int SAMPLE_RATE = 16000; // 16kHz
const int BIT_DEPTH = 16; // 16-bit
const int CHANNELS = 1; // Mono
const int BYTES_PER_SAMPLE = BIT_DEPTH / 8; // 2 bytes per sample
const int CHUNK_DURATION_MS = 500; // 500ms
const int MAX_CHUNK_SIZE = 64000; // 64 KB max per chunk
...
..
...
private void StartWebSocket(string webSocketUrl)
{
// define the websocket with binding sting and Token
ws = new WebSocket(webSocketUrl + weSocketUrlBinder + BearerToken);
// set the Security to Tls12
ws.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls13;
// activate extendet Login
// ws.Log.Level = LogLevel.Debug; // Debug, Info, Warn, Error, Fatal
// ws.Log.Output = (level, message) => Log($"[{level}] {message}\r\n".Replace($" ", $"\r\n").Replace("[" + DateTime.Now.ToString("dd.MM.yyyy"), "[" + DateTime.Now.ToString("dd.MM.yyyy")));
// if (string.IsNullOrEmpty(webSocketUrl)) return;
// Action Socket is Opening
ws.OnOpen += (sender, e) =>
{
// post Message
Log("WebSocket verbunden!\r\n");
// send the modified Confing from a Template direct after starting start
SendWebSocketConfig();
};
// Action Socket is running ad received data
ws.OnMessage += (sender, e) =>
{
// process receives data from Socket
if (e.IsText)
{
// fill received into Variabel
dynamic message = e.Data;
// Deserialize into Response Variabele
ConfigResponse message_json = JsonSerializer.Deserialize<ConfigResponse>(message);
//
if (message_json.type == "transcript")
{
Log($"[{message_json.type}] Transkripter Text: " + message.data.ToString());
}
else if (message_json.type == "CONFIG_ACCEPTED")
{
Log($"[{message_json.type}] Konfiguration akzeptiert.");
}
else if (message_json.type == "ENDED")
{
Log($"[{message_json.type}] Sitzung beendet.");
}
}
};
// Action Socket has a error
ws.OnError += (sender, e) =>
{
Log($"WebSocket Fehler: {e.Message}\r\n");
};
// Action Socket is closing
ws.OnClose += (sender, e) =>
{
//Log($"Verbindung geschlossen: {e.Reason}\r\n{e.Code}\r\n");
Log("WebSocket geschlossen!\r\n");
};
// start and connect the Websocket
ws.Connect();
}
...
..
...
private void StartAudioStreaming()
{
// Initialize microphone capture
waveIn = new WaveInEvent
{
WaveFormat = new WaveFormat(SAMPLE_RATE, BIT_DEPTH, CHANNELS),
BufferMilliseconds = CHUNK_DURATION_MS // 500ms buffer
};
// Audio data callback
waveIn.DataAvailable += (s, e) =>
{
int totalBytes = e.BytesRecorded;
int offset = 0;
while (offset < totalBytes)
{
int chunkSize = Math.Min(MAX_CHUNK_SIZE, totalBytes - offset);
byte[] chunk = e.Buffer.Skip(offset).Take(chunkSize).ToArray();
// If WS is open
if (ws.ReadyState == WebSocketState.Open)
{
// send the audio Chunk to the WS
ws.Send(chunk);
// Post message with the lenght of the chunk
Log($"Sent {chunk.Length} bytes...");
}
offset += chunkSize;
}
};
// Start recording
waveIn.StartRecording();
Log("Recording...");
}
}
}
vielleicht hast du ne Tip für mich...
Ich habe das mal minimal mit einem Client der Audiodaten sendet und einem Server der diese per WebSocket entpfängt nachgestellt und das hat einwandfrei funktioniert. Den Teil mit einer Konfig, die gesendet wird habe ich zwar weggelassen, allerdings ist die Aufnahme und Sende Logik exakt dieselbe wie die von dir genutzte.
Ansonsten fällt mir nichts wirklich auf was mit dem Problem zutun haben könnte. Um den Fehler weiter könntest du die Logs nehmen, die du dort machst. Es wäre gut zu wissen ob laut den Logs die Daten gesendet werden oder nicht, außerdem könntest du mit einem Haltepunkt in DataAvailable schauen, ob dort alles funktioniert wie es soll.
Ansonsten zwei Anmerkungen generel zum Quellcode: 1. Beim OnMessage nutzt du einen dynamic als Typ was unnötig ist wenn man Betrachtet das Data ein string ist und Deserialize auch einen string nimmt. 2. In DataAvailable nutzt du LINQ um den Chunk an data zusammen zustellen, was nicht effizient ist. Bei dem LINQ ansatz wird jedes einzelne der array Item duchgegangen. Stattdessen kann man Spans nutzen, die auf einen Bereich innerhalb des arrays verweisen. Man würde so eigentlich auch um das erstellen eines zweiten Chunk array drumherum kommen, allerdings unterstützt WebSocketSharp dies nicht.
Jetzt bin ich schon etwas vom eigentlichen Thema abgeschweift... Jedenfalls kann ich da ich nicht den gesamten Code geschweigedenn zugriff auf diesen Transkripierungs Service habe deine Bedingungen nur begrenzt nachstellen und den Rest müsstest du mithilfe von Debuggen oder mehr logging herausfinden. Eventuell kann man dir hier noch bei der deutung von den Logs oder beim Debuggen helfen.
Ok, dass hilft mir je erstmal in soweit weiter, dass ich nicht komplett auf dem Holzweg bin. Dafür erstmal vielen Dank.
Ich bin nun nicht ganz sicher ob mein Code im VS ein Problem auf meinen win11 ARM hat. Ich entwickele auf einem MacBook und Parallels. Würde jetzt nochmal versuchen die Chunks parallel in eine WAV zu speichern.
Wäre es möglich, dass du mir die „Serverseite“ zur Verfügung stellen könntest? Dann würde ich mir nen websocket mal aufsetzten und direkt testen.
Angehangen ist die Program.cs des simplen servers den ich benutzt habe. Dieser sendet zwar keine Daten an den Client zurück sowie es der von dir Verwendete Service macht und loggt nur die Anzahl an empfangenen Bytes was ausreichen sollte um zu testen ob die Audiodaten übertragen werden.
Ich bin nun nicht ganz sicher ob mein Code im VS ein Problem auf meinen win11 ARM hat. Ich entwickele auf einem MacBook
Das das Betriebssystem das du verwendest könnte dabei schon eine rolle spielen, da solche NAudio native API's verwendet wobei eigentlich Exceptions ausgelöst werden müssten wenn das der Fall wäre. Bei DataAvailable loggst du doch auch die menge an gesenteten Bytes müsste dort dann nicht auch nachvollziehbar sein ob überhaupt etwas gesendet wird?
Reiner Hinweis, da Du eine andere Lib verwendest:
Wir haben im Rahmen eines Microsoft AI Platform Use Cases mit Klemmbausteinen (technische Beschreibung hier) ebenfalls ein Audio-to-Server Streaming und vice versa; verwenden hier aber SignalR. SignalR verwendet dafür Channels (siehe SignalR Beispiel hier), was alles sehr simpel macht und keine weiteren Libs benötigt.
Willst Du bei NAudio bleiben, dann würde ich hier ein WebSocketBehavior umsetzen bzw hab ich das mal so für ne Demo gemacht, vielleicht funktioniert das noch:
// Server
class AudioReceiver : WebSocketBehavior
{
private readonly BufferedWaveProvider waveProvider;
private readonly WaveOutEvent waveOut;
public AudioReceiver()
{
WaveFormat waveFormat = new(44100, 16, 1); // client braucht die identischen Settings
waveProvider = new BufferedWaveProvider(waveFormat)
{
BufferLength = 8192 * 2, // Irgendein sinnvoller Buffer
DiscardOnBufferOverflow = true
};
// Mach irgendwas mit den Audio Daten hier
waveOut = new WaveOutEvent();
waveOut.Init(waveProvider);
waveOut.Play();
}
protected override void OnMessage(MessageEventArgs e)
{
if (e.IsBinary)
{
waveProvider.AddSamples(e.RawData, 0, e.RawData.Length);
}
}
protected override void OnClose(CloseEventArgs e)
{
// schließt die Verbindung
waveOut.Stop();
waveOut.Dispose();
}
}
Server Code sieht dann so aus:
static void Main()
{
WebSocketServer wssv = new("ws://0.0.0.0:8080");
wssv.AddWebSocketService<AudioReceiver>("/audio");
wssv.Start();
Console.WriteLine("WebSocket-Server läuft ws://localhost:8080/audio");
Console.WriteLine("Enter zum Beenden...");
Console.ReadLine();
wssv.Stop();
}
Der Client braucht hier kein Behavior:
static void Main()
{
string wsUrl = "ws://localhost:8080/audio";
using WebSocket ws = new (wsUrl);
ws.OnOpen += (sender, e) => Console.WriteLine("Verbunden mit dem Server.");
ws.OnClose += (sender, e) => Console.WriteLine("Verbindung zum Server geschlossen.");
ws.OnError += (sender, e) => Console.WriteLine("Fehler: " + e.Message);
ws.Connect();
Console.WriteLine("Connection hergestellt. Record startet...");
using (WaveInEvent waveIn = new())
{
waveIn.WaveFormat = new WaveFormat(44100, 16, 1); // Identische Settings
waveIn.BufferMilliseconds = 50;
waveIn.DataAvailable += (s, e) =>
{
ws.Send(e.Buffer);
};
waveIn.StartRecording();
Console.WriteLine("Record läuft. Enter to end.");
Console.ReadLine();
waveIn.StopRecording();
}
ws.Close();
}
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
@Abt Die Server Konponente läuft bei einem Dienstleister/Provider an welchen ich den Websocket nutzten möchte. Den Server Code wollte ich nur für interne Tests.
Ich habe lediglich die API Beschreibung https://docs.corti.ai/api-reference/stream#stream-configuration
Jetzt habe ich gerade den Code auf eine Windows10 Maschiene gezogen. Leider keine Verbesserung…
Bis auf das der Code auf der Win10 X64 gegenüber dem Win11 ARM dass das W10 mit TLS13 nicht klar kommt. Dort muss ich TLS12 setzten dennoch kommt von meinem Audio nichts an.
Im Gegensatz meine Config als Json wird entgegengenommen und mit einem „accepted“ quittiert.
Morgen bau ich mal den Testserver auf, vielleicht sieht das ein wenig mehr…
Die Logs habe ich jetzt schon mehr mals erwähnt, da diese helfen könnten den Fehler zu finden. Sind in den Logs nun überhaupt Einträge vorhanden, wie Sent ... bytes? Das würde ja schonmal helfen herauszufinden woran es jetzt genau liegt.
Ich hab Dienstag eine Troubleshooing Termin. Mal schauen was ich da an log rausbekomme.
Ich meine nicht die Server logs sondern die, die du mit der Log methode in deiner Anwendung anfertigst. Anhand von diesen Logs müsste ersichtlich sein ob die Audiodaten überhaupt gesendet werden was ja noch nicht ganz klar ist.
Da wird nichts geloggt. Leider!
Einzig mein Output wieviele Byte versendet wurde schiebt es in meine Textbox.
Besteht eine Möglichkeit dem ws.send() eine log zu entlocken?
Er meint Dein erstelles Logging; was steht da drin? Du hast ja Log() eingebaut. Wird Deine Stelle, an der Bytes gesendet werden, überhaupt errecht?
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Wenn es einen Fehler geben würde, würde er den Fwhöer melden. Das bekomm ich nicht.
Zeige das morgen mal…
Hi
So jetzt mal ein paar logs.
<<<Loglevel setzten>>>
[16.03.2025 15:26:41|Warn |Logger.set_Output|The current output action has been changed.]
<<<Verbdindung aufbauen>>>
[16.03.2025 15:26:41|Debug|WebSocket.sendHttpRequest|A request to the server:
GET /audio-bridge/v2/interactions/5606e448-636f-4a8e-aac7-b980e37d82d9/streams?tenant-name=copiloteu&token=Bearer%20eyJhbGciOiJSUzI1........RH5tNyqw HTTP/1.1
User-Agent: websocket-sharp/1.0
Host: api.beta-eu.corti.app
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: BVcT/L4TN9/YihqmOaMYtw==
Sec-WebSocket-Version: 13]
[16.03.2025 15:26:41|Debug|WebSocket.sendHttpRequest|A response to this request:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Date: Sun, 16 Mar 2025 14:26:41 GMT
Sec-Websocket-Accept: Dwyp2b2mfZp4jbc293ghGSNS68A=
Upgrade: websocket]
WebSocket verbunden!
<<<Senden der Json Config>>>
Send JSON-Konfiguration : {"Type":"config","Configuration":{"Transcription":{"PrimaryLanguage":"de","IsDiarization":false,"IsMultichannel":false,"Participants":[{"Channel":0,"Role":"multiple"}]},"Mode":{"Type":"facts","OutputLocale":"de"}}}
<<<Request im ws.OnMessage empfangen>>>
[CONFIG_ACCEPTED] Konfiguration akzeptiert.
<<<Nach dem start der Aufnahme über Button/StartAudioStreaming Funktion>>>
Recording...
Sent 16000 bytes...
Sent 16000 bytes...
Sent 16000 bytes...
Sent 16000 bytes...
Sent 16000 bytes...
Sent 16000 bytes...
Sent 16000 bytes...
Sent 16000 bytes...
Sent 16000 bytes...
<<<Nach dem Stop der Aufnahme über Button/StopAudioStreaming Funktion>>>
Audioaufnahme gestoppt.
Sent 9050 bytes...
<<<Nach dem Schließen desSockets über Button>>>
[16.03.2025 15:27:00|Fatal|WebSocket.<startReceiving>b__176_2|WebSocketSharp.WebSocketException: The header of a frame cannot be read from the stream.
at WebSocketSharp.WebSocketFrame.processHeader(Byte[] header)
at WebSocketSharp.WebSocketFrame.<>c__DisplayClass73_0.<readHeaderAsync>b__0(Byte[] bytes)
at WebSocketSharp.Ext.<>c__DisplayClass48_0.<ReadBytesAsync>b__0(IAsyncResult ar)]
[16.03.2025 15:27:00|Trace|WebSocket.close|Begin closing the connection.]
[16.03.2025 15:27:00|Debug|WebSocket.closeHandshake|Was clean?: False
sent: False
received: False]
[16.03.2025 15:27:00|Trace|WebSocket.close|End closing the connection.]
WebSocket geschlossen!
Sieht irgendwie so aus, als ob der ws.send(chunk) nichts sendet!
hab dann nochmal umgebaut. Wie @Abt geschrieben hatte den Recorder mit Sender gleich nach dem ws.connect()
private async void StartWebSocket(string webSocketUrl)
{
// define the websocket with binding sting and Token
ws = new WebSocket(webSocketUrl + weSocketUrlBinder + BearerToken);
Log("Socket definiert");
// set the Security to Tls12
ws.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12;
Log("Tls12 gesetzt");
// activate extendet Login
ws.Log.Level = LogLevel.Debug; // Debug, Info, Warn, Error, Fatal
ws.Log.Output = (level, message) => Log($"[{level}] {message}\r\n".Replace($" ", $"\r\n").Replace("[" + DateTime.Now.ToString("dd.MM.yyyy"), "[" + DateTime.Now.ToString("dd.MM.yyyy")));
// Action Socket is Opening
ws.OnOpen += (sender, e) =>
{
// post Message
Log("WebSocket verbunden!\r\n");
// send the modified Confing from a Template direct after starting start
SendWebSocketConfig();
};
// Action Socket is running ad received data
ws.OnMessage += (sender, e) =>
{
// process receives data from Socket
if (e.IsText)
{
// fill received into Variabel
dynamic message = e.Data;
// Deserialize into Response Variabele
ConfigResponse message_json = JsonSerializer.Deserialize<ConfigResponse>(message);
//
if (message_json.type == "transcript")
{
Log($"[{message_json.type}] Transkripter Text: " + message.data.ToString());
}
else if (message_json.type == "CONFIG_ACCEPTED")
{
Log($"[{message_json.type}] Konfiguration akzeptiert.");
}
else if (message_json.type == "ENDED")
{
Log($"[{message_json.type}] Sitzung beendet.");
}
}
};
// Action Socket has a error
ws.OnError += (sender, e) =>
{
Log($"WebSocket Fehler: {e.Message}\r\n");
};
// Action Socket is closing
ws.OnClose += (sender, e) =>
{
//Log($"Verbindung geschlossen: {e.Reason}\r\n{e.Code}\r\n");
Log("WebSocket geschlossen!\r\n");
};
// start and connect the Websocket
ws.Connect();
// audio
using (WaveInEvent waveIn = new())
{
waveIn.WaveFormat = new WaveFormat(44100, 16, 1); // Identische Settings
waveIn.BufferMilliseconds = 50;
waveIn.DataAvailable += (s, e) =>
{
ws.Send(e.Buffer);
Log("Daten versandt.");
};
waveIn.StartRecording();
Log("Record läuft. Enter to end.");
//waveIn.StopRecording();
}
}
da passiert gar nix. bringt mir nicht mal die "Daten versandt."
Sieht irgendwie so aus, als ob der ws.send(chunk) nichts sendet!
Da müsste eigentlich schon was gesendet werden, weil man dort 10 Logenträge sieht, die eben aussagen das Bytes gesendet wurde.
bringt mir nicht mal die
"Daten versandt."
Das ist wiederum seltsam, da beim ursprünglichen Code, die Loganweisungen direkt nach dem ws.Send(chunk)
ausgeführt wurden und jetzt irgendwie nicht.
Eigentlich müssten ja zumindest bei dem Versuch aus dem das Log ist Daten beim Server angekommen sein. Nur um ganz sicher zu gehen: Hast versuchst zu überprüfen ob die Daten per Websocket beim Server wirklich ankommen, also mithilfe eines minimalen lokalen Servers wie ich es probiert habe oder anderweitig?
Die 10 Einträge kommen von meiner Anweisung
// Post message with the lenght of the chunk
Log($"Sent {chunk.Length} bytes...");
Mit dem lokalem Server noch nicht. Mach ich dann gleich..
Also mit dem Testsyerver aufbau läuft alles!
Das Wavefile wird gespeichert und auch ein Send vom Server zum Client wird entgegengenommen.
Also sehe ich grundsätzlich den Fehler nicht auf meiner Seite.
Setzte nun auf das Date am Dienstag.
Wenn das Senden von Dir erfolgreich ist dann wäre meine nächste Vermutung, dass das Audio Format nicht stimmt bzw nicht akzeptiert wird.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ich habe nun den erneut modifiziert und erhalt nun nach dem Start des ws.send() folgendes Feedback
Recording...
[16.03.2025 18:05:58|Trace|WebSocket.close|Begin closing the connection.]
[16.03.2025 18:05:58|Debug|WebSocket.closeHandshake|Was clean?: True
sent: True
received: True]
[16.03.2025 18:05:58|Trace|WebSocket.close|End closing the connection.]
Also scheinbar wird wirklich das Audioformat nicht erkannt.
Die Beschreibung der API sagt zur Anforderung:
Ensure that your configuration was accepted before starting to send audio. We recommend sending audio in chunks of 500ms. In terms of buffering, the limit is 64000 bytes per chunk. Audio data should be sent as raw binary without JSON wrapping.
Die Doku ist nicht die beste, aber wenn ich corti ai audio format google komme ich auf https://help.corti.app/en/articles/10670937-supported-audio-formats wo die entsprechenden Formate gelistet sind.
Es gibt aber keine Angaben zu den eigentlichen Audio Settings, zB eben Hz etc.
Deine ganzen Settings wie
const int SAMPLE_RATE = 16000; // 16kHz
const int BIT_DEPTH = 16; // 16-bit
const int CHANNELS = 1; // Mono
const int BYTES_PER_SAMPLE = BIT_DEPTH / 8; // 2 bytes per sample
const int CHUNK_DURATION_MS = 500; // 500ms
const int MAX_CHUNK_SIZE = 64000; // 64 KB max per chunk
müssen ja irgendwie mit deren Backend matchen.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ich hab den Support nochmal angeschrieben. Vielleicht bekomme ich vor Dienstag schon eine Antwort.
Zitat von Abt
Wir haben im Rahmen eines Microsoft AI Platform Use Cases mit Klemmbausteinen (technische Beschreibung hier) ..
Was für ein tolles Projekt 😄
So Problem behoben!
Der gegenpart hat den gesendeten Audiostream nicht als WAV erkannt.
Ich habe in den ersten Bytes einfach den WAV Header gesendet und schon läufts..
Danke für eure Unterstützung!