Распознавание речи в реальном времени на C# с Foundry Local: модель на 0.67 ГБ и ни одного обращения к облаку
2026-08-04 в блоге .NET появился пример живой транскрипции с микрофона на Foundry Local и модели nemotron-speech-streaming-en-0.6b. Разбираем API сессии, ловушку с обратным давлением при передаче PCM и выбор пакета NuGet.
2026-08-04 Бруно Капуано опубликовал в блоге .NET статью Beyond Chat: live Speech-to-Text with Foundry Local and C#. Это консольное приложение, которое читает микрофон и печатает транскрипцию по мере того, как вы говорите, без единого сетевого вызова и без оплаты за токены. Интересна здесь не сама демонстрация, а то, что потоковое распознавание речи в SDK Foundry Local стало обычным асинхронным потоком.
Сессия и есть весь API
Foundry Local отдаёт живую транскрипцию через аудиоклиент, привязанный к загруженной модели. Никакого IChatClient здесь нет: это нативная поверхность SDK, а не Microsoft.Extensions.AI.
var config = new Configuration { AppName = "transcriber" };
await FoundryLocalManager.CreateAsync(config, logger);
var mgr = FoundryLocalManager.Instance;
await mgr.DownloadAndRegisterEpsAsync();
var catalog = await mgr.GetCatalogAsync();
var model = await catalog.GetModelAsync("nemotron-speech-streaming-en-0.6b")
?? throw new InvalidOperationException("Model not in catalog");
await model.DownloadAsync(p => Console.Write($"\r{p:F2}%"));
await model.LoadAsync();
var audioClient = await model.GetAudioClientAsync();
var session = audioClient.CreateLiveTranscriptionSession();
session.Settings.SampleRate = 16000;
session.Settings.Channels = 1;
session.Settings.Language = "en";
await session.StartAsync();
Дальше вы передаёте сырой 16-битный моно-PCM с частотой 16 кГц через session.AppendAsync(chunk) и читаете результаты из session.GetStream():
await foreach (var result in session.GetStream())
{
var text = result.Content?[0]?.Text;
if (result.IsFinal)
Console.WriteLine($"\n[FINAL] {text}");
else if (!string.IsNullOrEmpty(text))
Console.Write(text);
}
IsFinal это маркер состояния расшифровки, а не признак конца потока. Понимайте его как “этот фрагмент больше не будет пересматриваться” и продолжайте чтение.
Ловушка: обратный вызов NAudio синхронный
Захват микрофона использует NAudio.WaveInEvent, и его обработчик события DataAvailable синхронный, тогда как AppendAsync нет. Вызов AppendAsync прямо из обработчика порождает неограниченные вызовы по принципу “запустил и забыл”, которые игнорируют обратное давление SDK. В примере звук идёт через ограниченный канал:
var audioChannel = Channel.CreateBounded<byte[]>(
new BoundedChannelOptions(50) { FullMode = BoundedChannelFullMode.DropOldest });
var appendTask = Task.Run(async () =>
{
await foreach (var chunk in audioChannel.Reader.ReadAllAsync())
await session.AppendAsync(chunk);
});
waveIn.DataAvailable += (s, e) =>
{
var buffer = new byte[e.BytesRecorded];
Buffer.BlockCopy(e.Buffer, 0, buffer, 0, e.BytesRecorded);
audioChannel.Writer.TryWrite(buffer);
};
Отбрасывать самый старый фрагмент в 100 мс под нагрузкой это правильное решение для живых субтитров. Блокировать аудиообработчик неправильное.
Какой пакет и какая модель
В Windows ставьте Microsoft.AI.Foundry.Local.WinML: он работает поверх среды выполнения Windows ML и даёт более широкое аппаратное ускорение. На остальных платформах Microsoft.AI.Foundry.Local. Поверхность API в обоих случаях одинакова. Для захвата добавьте NAudio, но учтите, что WaveInEvent работает только в Windows, поэтому на других системах пример переключается на синтетический PCM. Документация указывает минимальной планкой .NET 9.0 SDK или новее; пример из блога нацелен на .NET 10.
Английская модель nemotron-speech-streaming-en-0.6b квантована с 2.47 ГБ до 0.67 ГБ и работает на CPU быстрее реального времени. Если английского мало, замените псевдоним на nvidia-nemotron-3.5-asr-streaming-multilingual-0.6b и задайте session.Settings.Language = "auto".
Завершайте вызовами session.StopAsync() и model.UnloadAsync(), а когда захотите вернуть место на диске, используйте model.RemoveFromCacheAsync(). Полное пошаговое руководство есть в статье Microsoft Learn.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.