Speech Recognition in C# with VK Cloud: Low-Level Hooks and NAudio
A developer created a console application in C# that captures speech via microphone, sends audio to the VK Cloud Speech API, and inserts recognized text into the clipboard. Activation is managed through global keyboard hooks: holding down the right Ctrl key starts recording, releasing it triggers processing. NAudio handles WAV recording (16 kHz, 16-bit, mono), while HttpClient manages API requests with a 15-second timeout.
The code is Windows-specific and requires running as administrator due to SetWindowsHookEx. The VK Cloud token is hardcoded as a constant—this is a service key from your personal dashboard.
Global Hooks for Recording Activation
The low-level keyboard hook (WH_KEYBOARD_LL) intercepts WM_KEYDOWN/WM_KEYUP events. Pressing the right Ctrl key (0xA3) asynchronously starts/stops recording. Pressing ESC exits the application.
Key P/Invoke declarations:
[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);
In HookCallback, vkCode from lParam is checked. If !isRecording && !isProcessing, a Task.Run(() => StartRecordingAsync()) is initiated.
Audio Capture and Processing
WaveInEvent is configured at 16,000 Hz. DataAvailable writes data to a temporary voice_temp.wav file using WaveFileWriter. After stopping, the file size is checked (>1,000 bytes), then recognition proceeds.
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);
In StopRecordingAndProcessAsync: resources are disposed, a 200ms delay occurs, then RecognizeSpeechAsync is called with a Bearer token.
Integration with VK Cloud Speech API
POST request to https://voice.mcs.mail.ru/asr with audio/wave content type. Response is parsed using 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);
Error handling includes Unauthorized (expired token), timeouts, and HTTP status codes. Logs appear in the console with emojis for visual feedback.
- Success: Text is placed in clipboard, log shows "✅ Recognized".
- Short file: Skipped if under 1,000 bytes.
- API error: Beep sound + details displayed.
Inserting Text via STA Thread
Clipboard.SetText requires an STA (Single-Threaded Apartment) context. A separate thread is created with SetApartmentState(STA), then Join() waits for completion.
Thread staThread = new Thread(() => {
Clipboard.SetText(text, TextDataFormat.Text);
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
staThread.Join();
Alternative approaches include SendKeys or UI Automation, but this method works reliably via Ctrl+V.
Key Points
- Global hooks: WH_KEYBOARD_LL requires admin privileges and injects into any process.
- Audio format: 16 kHz / 16-bit / mono — optimal for VK Cloud ASR.
- Asynchronicity: Task.Run prevents blocking the hook.
- Security: Token embedded in code — intended for personal use only; never commit to repositories.
- Limitations: Clipboard-based output instead of direct input; storage cost ~3 RUB per article.
— Editorial Team
No comments yet.