Rozpoznawanie mowy w C# z VK Cloud: niskopoziomowe hooki i NAudio
Rozwój aplikacji konsolowej w C#, która przekształca głos przez mikrofon, wysyła dźwięk do API rozpoznawania mowy VK Cloud i wkleja rozpoznaną treść do schowka. Sterowanie odbywa się za pomocą globalnych hooków klawiatury: przytrzymanie prawego Ctrl aktywuje nagrywanie, puścienie — przetwarzanie. Do nagrywania wykorzystano NAudio (format WAV: 16 kHz, 16 bitów, mono), a żądania API są realizowane przez HttpClient z timeoutem 15 sekund.
Kod jest przeznaczony dla systemu Windows i wymaga uruchomienia z uprawnieniami administratora ze względu na użycie SetWindowsHookEx. Token do VK Cloud jest wprowadzany jako stała — klucz usługowy z panelu użytkownika.
Globalne hooki do aktywacji nagrywania
Niskopoziomowy hook klawiatury (WH_KEYBOARD_LL) przechwytuje zdarzenia WM_KEYDOWN/WM_KEYUP. Prawy klawisz Ctrl (0xA3) uruchamia lub zatrzymuje nagrywanie asynchronicznie. Klawisz ESC kończy działanie aplikacji.
Kluczowe deklaracje P/Invoke:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
W HookCallback sprawdzana jest wartość vkCode z lParam. Jeśli !isRecording && !isProcessing, uruchamiany jest Task.Run(() => StartRecordingAsync()).
Przechwytywanie i przetwarzanie dźwięku
WaveInEvent konfiguruje się na częstotliwość 16 000 Hz. Zdarzenie DataAvailable zapisuje dane do tymczasowego pliku voice_temp.wav za pomocą WaveFileWriter. Po zakończeniu nagrywania sprawdzany jest rozmiar pliku (>1000 bajtów), po czym następuje rozpoznawanie mowy.
var waveFormat = new WaveFormat(16000, 16, 1);
waveIn = new WaveInEvent { WaveFormat = waveFormat };
writer = new WaveFileWriter(OutputFileName, waveIn.WaveFormat);
waveIn.DataAvailable += (s, e) => writer?.Write(e.Buffer, 0, e.BytesRecorded);
W StopRecordingAndProcessAsync: zwalniane są zasoby, dodatkowa opóźnienie 200 ms, następnie wywoływana jest funkcja RecognizeSpeechAsync z tokenem Bearer.
Integracja z API rozpoznawania mowy VK Cloud
Zapytanie POST na https://voice.mcs.mail.ru/asr z treścią typu audio/wave. Odpowiedź parsowana jest przez Newtonsoft.Json.Linq: result.texts[0].punctuated_text.
var request = new HttpRequestMessage(HttpMethod.Post, "https://voice.mcs.mail.ru/asr");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = content;
var response = await httpClient.SendAsync(request);
Obsługa błędów: Unauthorized (token wygasł), timeout, błędy HTTP. Logi wyświetlane są w konsoli z emotikonami do wizualnej sygnalizacji.
- Sukces: tekst wklejony do schowka, log "✅ Rozpoznano".
- Krótki plik: pominięcie (<1000 bajtów).
- Błąd API: sygnał dźwiękowy + szczegółowe informacje.
Wklejanie tekstu przez wątek STA
Clipboard.SetText wymaga środowiska STA (Single-Threaded Apartment). Tworzony jest osobny wątek z ustawionym stanem SetApartmentState(STA), a Join() oczekuje na jego zakończenie.
Thread staThread = new Thread(() => {
Clipboard.SetText(text, TextDataFormat.Text);
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
staThread.Join();
Alternatywa: SendKeys lub UI Automation, ale obecny sposób działa poprzez Ctrl+V.
Co warto pamiętać
- Globalne hooki: WH_KEYBOARD_LL wymaga uprawnień administratora i może być wstrzykiwany do dowolnego procesu.
- Format dźwięku: 16 kHz / 16 bitów / mono — optymalny dla ASR VK Cloud.
- Asynchroniczność: Task.Run zapobiega blokadzie hooka.
- Bezpieczeństwo: token w kodzie — tylko do użytku prywatnego, nie wypychaj go do repozytorium.
- Ograniczenia: schowek zamiast bezpośredniego wklejenia, koszt magazynowania ok. 3 zł za artykuł.
— Editorial Team
Brak komentarzy.