An utterance is a stretch of speech; an endpoint is the event that closes it.
dotnet add package RecursiveAI.OneSpeechRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
Use the zip’s sample.flac audio.
using OneSpeech; using OneSpeech.Audio; using OneSpeech.Live; using OneSpeech.Moonshine; using OneSpeech.Vad; var maximum = TimeSpan.FromSeconds(1); using var recognizer = await MoonshineRecognizer.LoadAsync( model: "moonshine-ai/moonshine-tiny"); using var vad = new SileroVad(); using var session = new UtteranceLiveSession( recognizer, vad, "en", maximumUtteranceDuration: maximum); await foreach (var update in session.Listen( Pcm(), CancellationToken.None)) if (update is EndpointEvent endpoint) Console.WriteLine( $"{endpoint.Reason}: " + $"end={endpoint.End}, " + $"available={endpoint.AudioTime}"); static async IAsyncEnumerable<AudioBuffer> Pcm() { using var input = AudioFileSource.Open( "sample.flac", CancellationToken.None); var buffer = new float[1600]; // 100 ms mono. int count; while ((count = input.Read( buffer, CancellationToken.None)) != 0) { yield return new AudioBuffer( buffer.AsMemory(0, count), 16000); await Task.Yield(); } }
First run downloads Moonshine tiny weights (about 108 MB). Change the model cache.
The zip contains the unmodified LibriSpeech test-clean utterance 1089-134686-0001: “stuff it into you his belly counselled him”. Credit: Vassil Panayotov, Guoguo Chen, Daniel Povey and Sanjeev Khudanpur (LibriSpeech), CC BY 4.0.
dotnet run prints:
DurationLimit: end=00:00:01.5680000, available=00:00:01.6000000 DurationLimit: end=00:00:02.5920000, available=00:00:02.6000000 Silence: end=00:00:02.9100000, available=00:00:03.1000000
UtteranceLiveSession borrows the recognizer and detector; the declaration order disposes the session first. It runs a finals-only recognizer and a voice activity detector (VAD), which finds speech in the incoming samples. Pcm replays the mono recording in 100 ms blocks; it does not capture a device.
The maximum is measured from the speech start, not the beginning of the file. The first speech starts after initial silence, which is why the first endpoint is later than one second. AudioTime is the accepted-input clock, not a word boundary.
Missing moonshine-ai/moonshine-tiny files download on first use. An utterance maximum outside one through 300 seconds throws ArgumentOutOfRangeException.
Variations
Wait longer for silence. Add options: new SileroVadOptions { MinimumSilence = TimeSpan.FromSeconds(1) } to the UtteranceLiveSession constructor in the full program. The sample ends before that much trailing silence arrives, so completion flushes the last utterance:
DurationLimit: end=00:00:01.5680000, available=00:00:01.6000000 DurationLimit: end=00:00:02.5920000, available=00:00:02.6000000 EndOfInput: end=00:00:03.2750000, available=00:00:03.2750000
The duration-limited closures still occur even though speech continues.
Related recipes
Label speakers in live audio in C# · Display partial speech recognition results in C# · Transcribe audio from a stream in C#
The short answer
Create UtteranceLiveSession with maximumUtteranceDuration, then process its EndpointEvent values. A duration-limited endpoint can close while speech continues; the following samples belong to the next utterance. Call Complete at normal end-of-input, or enumerate Listen to its normal end so it completes for you.
How do I limit live speech utterance duration in C#?
The constructor accepts a maximum from one through 300 seconds, with a default of 30 seconds. This is an utterance bound, not a recording-length limit. Continuous speech is divided into consecutive utterances without dropping samples at a forced cut. Do not end your capture stream just because one duration endpoint arrives.
The implementation evaluates VAD in frames. A duration cut occurs at the next frame, at most 32 milliseconds beyond the configured maximum. That is why the one-second example does not produce endpoints at exact whole-second timestamps. The speech also starts after the recording begins; initial silence does not consume an utterance’s speech-duration budget. These bounds come from the pinned live-session documentation.
The session borrows a recognizer and detector. Dispose it before those models, and do not share one session concurrently across independent streams. The sample uses an offline Moonshine recognizer, so it demonstrates endpoint behavior with finals-only recognition. This sample shows only final text, not partial results.
How do I configure a live speech endpoint in C#?
Use SileroVadOptions for the speech activity policy and the session constructor for the utterance maximum. MinimumSilence controls how much quiet closes an utterance. It does not override the duration maximum. The variation waits for a longer silent gap but still emits the same duration-limit endpoints during continuous speech.
Keep the endpoint reason rather than reducing all closures to “stopped talking.” Silence means the configured quiet gap occurred. DurationLimit means the bounded utterance was committed even if someone is still speaking. EndOfInput means the caller finished the actual source. A UI can use these distinctions to decide whether to keep listening or close a recording, without guessing from punctuation.
VAD padding and the retained segment interval are audio activity bounds, not word boundaries. The endpoint’s End names retained audio. AudioTime says how much input had been accepted when the update became available. Large pushes can make several events share one availability time; smaller blocks give finer availability observations. None of these values is a wall-clock timestamp.
What is endpointing?
Endpointing closes an utterance, a retained stretch of speech, because of silence, a duration limit or end-of-input. Closing an utterance does not necessarily end the whole source.
Normal async enumeration invokes Complete, which flushes pending real samples without appending artificial silence. Explicit completion can retain trailing silence shorter than the endpoint threshold. The variation therefore ends at the file’s real end when no long-enough quiet gap has arrived.
The final, if any, precedes the endpoint. Keep final-text handling independent from endpoint handling: an utterance can close without usable recognized text. A silence-only input need not produce an endpoint at all. Do not create empty words or a fake final to make the event counts match.
Cancellation and early iterator disposal do not flush. They stop work, rather than asserting the speaker reached a natural boundary. After processing faults, create a new session. Retrying an ambiguously consumed block could shift the input clock and corrupt later interpretation. Capturing a microphone and resampling into mono 16 kHz remain caller responsibilities.