Speech to Text in C#, Offline: Whisper in Your .NET Process
Transcribe audio files from C# with OpenAI's Whisper running locally: no API key, no Python, no whisper.cpp build. One NuGet package, timestamps per segment, and 3 minutes of speech transcribed in 25 seconds on a CPU.
Transcribing audio from C# usually means sending it to a cloud API, or building whisper.cpp and wrapping it yourself. This runs OpenAI's Whisper model inside your .NET process, from one NuGet package, with no network after the first download:
using Kjarni;
using var transcriber = new Transcriber("whisper-small", language: "en", quiet: true);
var transcript = transcriber.TranscribeFile("jfk.wav");
Console.WriteLine(transcript.Text);
And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country.
Try it
You need the .NET 8 SDK.
dotnet new console -o SpeechToText
cd SpeechToText
dotnet add package Kjarni
curl -LO https://github.com/ggml-org/whisper.cpp/raw/master/samples/jfk.wav
Replace Program.cs with:
using System.Diagnostics;
using Kjarni;
var path = args.Length > 0 ? args[0] : "jfk.wav";
using var transcriber = new Transcriber("whisper-small", language: "en", quiet: true);
var sw = Stopwatch.StartNew();
var transcript = transcriber.TranscribeFile(path);
Console.WriteLine(transcript.Text);
Console.WriteLine();
foreach (var segment in transcript.Segments)
Console.WriteLine($"[{segment.Start,6:F2} -> {segment.End,6:F2}] {segment.Text}");
Console.Error.WriteLine($"\n{transcript.DurationSecs:F1} s of audio transcribed in {sw.Elapsed.TotalSeconds:F1} s");
dotnet run
The first run downloads whisper-small (920 MB) and caches it. jfk.wav is the 11-second
sample from the whisper.cpp repository, a public domain recording of John F. Kennedy.
A longer recording
For something with more than one sentence, here is a 3 minute 19 second public domain address from Wikimedia Commons, converted to WAV with ffmpeg:
curl -L -o columbia.ogg https://upload.wikimedia.org/wikipedia/commons/1/1f/George_W_Bush_Columbia_FINAL.ogg
ffmpeg -i columbia.ogg -ar 16000 -ac 1 columbia.wav
dotnet run -- columbia.wav
My fellow Americans, this day has brought terrible news and great sadness to our country. At 9 o'clock this morning, ...
[ 0.00 -> 9.00] My fellow Americans, this day has brought terrible news and great sadness to our country.
[ 9.00 -> 18.00] At 9 o'clock this morning, Mission Control in Houston lost contact with our space shuttle Columbia.
[ 18.00 -> 24.00] A short time later, debris was seen falling from the skies above Texas.
[ 24.00 -> 27.00] The Columbia's lost.
[ 27.00 -> 29.00] There are no survivors.
[ 29.54 -> 37.14] On board was a crew of seven, Colonel Rick Husband, Lieutenant Colonel Michael Anderson,
...
198.7 s of audio transcribed in 25.1 s
Segments carries start and end times in seconds, which is what you need for subtitles,
for jumping to a moment in a player, or for searching a recording by what was said.
How fast, and how good
On an Intel i7-13700 (24 threads), with the model already downloaded:
| Model | Download | 3 min 19 s of speech | Faster than real time |
|---|---|---|---|
whisper-small | 920 MB | 25 s | 8× |
whisper-large-v3 | 2.9 GB | 124 s | 1.6× |
whisper-small got almost all of the address right. It dropped one short phrase ("In the words of the prophet Isaiah"), which large-v3 kept, and both models misheard one name: astronaut Kalpana Chawla came out as "Kultna Shavla". That is typical of Whisper: common words are reliable, rare names are not. Check names, numbers and terms of art before you publish a transcript.
Use whisper-small by default, and large-v3 when accuracy matters more than time, for accented speech or for languages other than English.
Formats: WAV in, ffmpeg for the rest
TranscribeFile reads WAV files, at any sample rate, mono or stereo; Kjarni resamples to
the 16 kHz mono Whisper expects. Kjarni 0.4.2 does not decode MP3, Ogg or FLAC, so
convert those first:
ffmpeg -i meeting.mp3 -ar 16000 -ac 1 meeting.wav
If you already have the audio in memory, from a microphone or a decoder library such as NAudio, pass the samples directly:
float[] samples = ...; // mono, -1.0 to 1.0
var transcript = transcriber.Transcribe(samples, sampleRate: 16000);
Languages and translation
Whisper is multilingual. Pass the language if you know it, or null to let the model
detect it from the first seconds of audio:
using var detect = new Transcriber("whisper-small", language: null, quiet: true);
var t = detect.TranscribeFile("interview.wav");
Console.WriteLine(t.Language); // e.g. "de"
translate: true turns speech in any supported language into English text in the same
pass:
using var toEnglish = new Transcriber("whisper-small", translate: true, quiet: true);
What it does not do
- No live streaming from a microphone. It transcribes files or sample buffers that you have. For near-live captions, transcribe chunks of a few seconds as they arrive.
- No speaker labels. Whisper does not say who is speaking.
- CPU by default.
device: "gpu"uses a GPU through Vulkan, DirectX 12 or Metal.
FAQ
How do I convert speech to text in C# offline?
Install the Kjarni NuGet package and call new Transcriber("whisper-small").TranscribeFile("audio.wav").
The Whisper model runs inside your process; after the first download nothing goes over
the network.
Does it need Python, whisper.cpp or an API key?
No. The package includes the native library for Windows x64, Linux x64 and arm64, and macOS on Apple Silicon.
Which audio formats does it read?
WAV, at any sample rate, mono or stereo. Convert MP3, Ogg, FLAC and video files to WAV with ffmpeg first.
How accurate is it?
whisper-small gets everyday English nearly word for word and makes mistakes on rare names. whisper-large-v3 is more accurate and about five times slower.
Getting it
Install: dotnet add package Kjarni
NuGet: https://www.nuget.org/packages/Kjarni
GitHub: https://github.com/olafurjohannsson/kjarni
Related
- Speech to text in Qt: the same Whisper models in a Qt Quick app
- Local LLM Chat in C#: summarize or question the transcript locally
- Transcriber reference
Kjarni runs the same engine on every platform: