Laden...

Websocket öffnen und Audiostream Uploaden um diesen zu transkribieren.

Letzter Beitrag vor 6 Tagen 24 Posts 465 Views
Websocket öffnen und Audiostream Uploaden um diesen zu transkribieren.

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();
}

@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?

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.

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.

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!