Print text piece by piece, so audio memory stays flat on long files. This streaming call does not assign whole-recording speaker labels.
dotnet add package RecursiveAI.OneSpeechRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using OneSpeech; await foreach (var segment in Speech.TranscribeStream("speech.flac", "en", "moonshine-ai/moonshine-streaming-tiny")) Console.WriteLine(segment.Text);
Prints final text as chunks finish. speech.flac is in the zip:
Unzip the example, then dotnet run.
Stuff it into you, his belly counseled him.Full program
speech.flac is the unmodified LibriSpeech test-clean recording 1089-134686-0001, renamed for this example. It contains:
Stuff it into you, his belly counselled him.
LibriSpeech, Vassil Panayotov, Guoguo Chen, Daniel Povey and Sanjeev Khudanpur (2015); CC BY 4.0. The zip includes attribution. The sample is short so the download stays small. The model really prints “counseled,” even though the supplied text uses “counselled.”
The full program accepts your recording with dotnet run -- meeting.wav. Missing models download on first use; this example selects the English Moonshine streaming-tiny recognizer explicitly. Cancellation throws OperationCanceledException; invalid audio throws UnsupportedAudioException.
using OneSpeech; string path = args.Length == 0 ? "speech.flac" : args[0]; await foreach (var segment in Speech.TranscribeStream( path, language: "en", model: "moonshine-ai/moonshine-streaming-tiny")) Console.WriteLine(segment.Text);
Output from dotnet run:
Stuff it into you, his belly counseled him.Variations
Read encoded audio streams instead of paths. This replaces the full program and prints the same output above. The streams remain open until the using declarations dispose them.
using OneSpeech; using var stream = File.OpenRead("speech.flac"); await foreach (var segment in Speech.TranscribeStream( stream, language: "en", model: "moonshine-ai/moonshine-streaming-tiny")) Console.WriteLine(segment.Text);
Print segment timestamps on a long file. This replaces the full program. For a reproducible 131-second recording, repeat the zip’s redistributable LibriSpeech clip 40 times. The source is LibriSpeech test-clean utterance 1089-134686-0001, released under CC BY 4.0 with the attribution above. This repeated clip demonstrates chunked output, not recognition quality on a varied conversation.
using OneSpeech; string path = args.Length == 0 ? "speech.flac" : args[0]; await foreach (var segment in Speech.TranscribeStream( path, language: "en", model: "moonshine-ai/moonshine-streaming-tiny")) Console.WriteLine($"{segment.Start?.TotalSeconds:0.00}–{segment.End?.TotalSeconds:0.00}: {segment.Text}");
With FFmpeg installed, create the longer file from speech.flac and run it:
ffmpeg -v error -stream_loop 39 -i speech.flac -c:a flac long.flac -y dotnet run -- long.flac
The first six of 40 segments from that run (times are seconds):
0.55–2.91: Stuff it into you, his belly counseled him. 3.62–6.17: Stuff it into you, his belly counseled him. 6.88–9.47: Stuff it into you, his belly counseled him. 10.15–12.73: Stuff it into you, his belly counseled him. 13.44–16.00: Stuff it into you, his belly counseled him. 16.71–19.29: Stuff it into you, his belly counseled him.
The final segment ran from 128.07 to 130.62 seconds. “Counseled” is the model’s actual output, not a corrected transcript. The longer recording is generated locally rather than included in the zip.
Related recipes
The short answer
Use Speech.TranscribeStream and consume each segment with await foreach. It decodes the recording in bounded chunks and lets you write each result without building one large transcript. Choose Speech.Transcribe instead when you need a complete document with whole-recording speaker labels. Neither call needs to retain the entire PCM waveform, according to the long-file documentation.
How do I transcribe a long audio file?
Start by choosing what your application needs to retain. For a running text log, print or save each final segment and let it go. For a complete transcript, use the document-returning API and budget memory for the returned words, turns and text. Bounded audio storage does not make the result document constant-size.
The program above uses file streaming. Its chunks are found by voice activity detection: the library locates speech rather than treating every part of the recording as spoken text. Results are final segments, not replacing partial hypotheses. They arrive as chunks finish, not one word at a time on the microphone’s clock. You do not need to build a manual audio-splitting loop for this file API.
Pass the path to an existing, unchanged file. Do not overwrite or append to it while the decoder is processing it. If you use a caller-owned encoded stream, the library may spool it to a temporary seekable file. Allow disk space for that temporary copy. Finish enumeration or dispose it early to release the decoder, models and temporary resources. A break from await foreach disposes the enumerator; abandoning work without disposing it is not a cleanup strategy.
The tiny English recognizer used here is an explicit selection, not the default for every transcription call. The file default is Whisper large-v3-turbo. Model IDs select roles, not an entire set of companion models. Switching from segment streaming to a full document can introduce additional timing and diarization work.
How long does it take to transcribe 30 minutes of audio?
There is no fixed duration ratio for every machine and model. First-use downloads and local model conversion can dominate startup. A warm-cache run and a first run are different workloads. Budget time for acquisition separately from recognition, and pre-fetch models before an interactive request if startup latency matters.
Cancellation is cooperative: the token is checked at audio blocks, chunk boundaries and supported model operations. It is not an instruction-level interrupt. Similarly, audio time processed is not an estimate of remaining wall-clock time. For a cancellable job, pass your application’s token as cancellationToken and handle OperationCanceledException at the job boundary.
Chunk-boundary speech can affect output. The library does not promise that streamed text is token-identical to a whole-file reference policy. Test representative recordings with long pauses, speech near chunk edges and changing voices. The short downloadable sample demonstrates how to consume results; it is not a long-recording performance claim.
Can I transcribe a long audio file for free in C#?
OneSpeech is free and open source; the example runs local open-weight inference rather than a metered transcription endpoint. You still supply storage, memory and compute. Missing model files download on first use, so the NuGet package alone is not an offline deployment. See the model download guide for cache preparation and the selected model’s license. OneSpeech is unaffiliated with other products or companies named OneSpeech.