Voz a texto y subtítulos en vivo — SpeechToTextBlock¶
SpeechToTextBlock es un Media Block exclusivamente de audio de VisioForge.DotNet.Core.AI.Whisper. Intercepta el flujo de audio, segmenta la voz con Silero VAD, la transcribe con Whisper (Whisper.net / GGML) y dispara OnSpeechRecognized. El audio pasa sin cambios. El bloque implementa IAudioProcessingBlock, por lo que puede insertarse en un pipeline manual o registrarse directamente en VideoCaptureCoreX/MediaPlayerCoreX.
using VisioForge.Core.MediaBlocks.AI;
using VisioForge.Core.Types.X.AI;
Configuración básica del bloque¶
var settings = new SpeechToTextSettings(whisperModelPath)
{
Language = "auto",
Task = SpeechToTextTask.Transcribe,
EnableVad = true,
EmitInterim = false,
};
settings.Vad.ModelPath = sileroVadModelPath;
var stt = new SpeechToTextBlock(settings);
stt.OnSpeechRecognized += (sender, e) =>
{
foreach (var segment in e.Segments)
{
Console.WriteLine($"{segment.StartTime:c} - {segment.EndTime:c}: {segment.Text}");
}
};
SpeechToTextTask.Transcribe conserva el idioma de origen. SpeechToTextTask.Translate traduce la voz de origen admitida a texto en inglés.
Configuración clave¶
SpeechToTextSettings(whisperModelPath). A diferencia de la configuración de IA de visión, este tipo no deriva de OnnxInferenceSettings — Whisper se ejecuta a través de Whisper.net (whisper.cpp / GGML), no de ONNX Runtime, por lo que los parámetros específicos de ONNX (tamaño de entrada, normalización) no aplican.
| Propiedad | Valor predeterminado | Descripción |
|---|---|---|
WhisperModelPath | — | Ruta absoluta al archivo del modelo Whisper GGML (ggml-*.bin). Obligatorio. |
ModelSize | WhisperModelSize.Base | Etiqueta informativa de la variante de modelo en WhisperModelPath (ver abajo). |
Language | "auto" | Código ISO 639-1 ("en", "es", "fr", ...), o "auto" para que Whisper lo detecte. |
Task | Transcribe | Transcribe (idioma de origen) o Translate (a inglés). |
Provider | Auto | Solo CPU y CUDA tienen sentido para el backend GGML (sin DirectML); Auto elige CUDA si está presente, de lo contrario CPU. |
DeviceId | 0 | ID del dispositivo de hardware cuando se selecciona un proveedor de GPU. |
Threads | 0 | Hilos de CPU que usa Whisper. 0 deja que Whisper.net elija según el número de procesadores disponibles. |
EnableVad | true | Segmenta la voz con Silero VAD antes de la transcripción. Cuando es false, el audio se transcribe en ventanas fijas, lo que tiende a alucinar texto durante el silencio. |
Vad | nuevo SileroVadSettings | Configuración de VAD usada cuando EnableVad es true. |
FixedWindowSeconds | 5 | Longitud de la ventana de transcripción fija cuando EnableVad es false. Limitado entre 1 y 30 s. |
EmitInterim | false | Reservado para una futura capacidad de hipótesis provisional; actualmente no tiene efecto — solo se emiten segmentos finales. |
OutputSrtPath | null | Ruta opcional del archivo lateral .srt que el bloque escribe a medida que se reconocen los segmentos finales. |
OutputVttPath | null | Ruta opcional del archivo lateral .vtt (WebVTT) que el bloque escribe a medida que se reconocen los segmentos finales. |
Configuración de VAD¶
Cuando EnableVad es true, SpeechToTextSettings.Vad controla la segmentación de voz de Silero. Silero VAD es un modelo ONNX diminuto (~2 MB, MIT) que clasifica ventanas cortas de audio como voz o no-voz, usado como filtro previo en tiempo real para que el modelo Whisper (mucho más pesado) solo se ejecute sobre voz real.
settings.Vad = new SileroVadSettings
{
ModelPath = sileroVadModelPath,
SpeechThreshold = 0.5f,
MinSilenceMs = 100,
MinSpeechMs = 250,
SpeechPadMs = 30,
MaxSpeechMs = 15000,
Provider = OnnxExecutionProvider.CPU,
};
| Propiedad | Valor predeterminado | Descripción |
|---|---|---|
ModelPath | — | Ruta absoluta a silero_vad.onnx. |
SpeechThreshold | 0.5 | Umbral de probabilidad de voz (0..1). Súbalo en entornos ruidosos para reducir falsos disparos. |
MinSilenceMs | 100 | Silencio final mínimo, en ms, que termina un segmento de voz. |
MinSpeechMs | 250 | Duración mínima de una racha de voz, en ms, para ser emitida (descarta ruidos espurios). |
SpeechPadMs | 30 | Relleno de inicio, en ms, añadido antes de cada segmento detectado. |
MaxSpeechMs | 15000 | Longitud máxima de segmento, en ms, antes de que el segmentador corte forzosamente una racha continua. |
Provider | CPU | Proveedor de ejecución para la sesión de VAD — el modelo es diminuto (~1 ms/ventana en CPU), por lo que la GPU añade latencia sin beneficio. |
DeviceId | 0 | ID del dispositivo de hardware cuando se selecciona un proveedor de GPU. |
Los pesos GGML de Whisper y el modelo Silero VAD se descargan en tiempo de ejecución; ninguno se distribuye en los paquetes NuGet del SDK.
Tamaños de modelo de Whisper¶
WhisperModelSize es informativo — nombra archivos de pesos GGML de Whisper conocidos para que una aplicación pueda elegir cuál descargar. El archivo realmente cargado siempre es SpeechToTextSettings.WhisperModelPath.
| Valor | Tamaño aprox. | Notas |
|---|---|---|
Tiny | ~75 MB | El más rápido, menor precisión. Bueno para transcripción en tiempo real en CPU. |
Base (predeterminado) | ~142 MB | Un buen valor predeterminado en tiempo real para CPU. |
Small | ~466 MB | Notablemente más preciso; tiempo real con GPU o una CPU rápida. |
Medium | ~1.5 GB | Alta precisión; normalmente necesita GPU para tiempo real. |
LargeV3 | ~3 GB | Máxima precisión; se recomienda encarecidamente una GPU. |
LargeV3Turbo | ~1.6 GB | Precisión cercana a la de los modelos grandes a una fracción del costo. |
TinyQuantized | ~31 MB | Tiny, cuantizado con Q5_1. |
BaseQuantized | ~57 MB | Base, cuantizado con Q5_1. |
SmallQuantized | ~181 MB | Small, cuantizado con Q5_1. |
MediumQuantized | ~514 MB | Medium, cuantizado con Q5_0. |
LargeV3TurboQuantized | ~547 MB | LargeV3Turbo, cuantizado con Q5_0. Equilibrio recomendado entre precisión y velocidad cuantizadas. |
Las variantes solo en inglés (*.en) no están enumeradas; indique su ruta directamente. Los pesos tienen licencia MIT.
Segmentos reconocidos¶
Cada SpeechSegment en SpeechRecognizedEventArgs.Segments:
| Propiedad | Descripción |
|---|---|
Text | El texto reconocido para el segmento. |
StartTime / EndTime | TimeSpan, relativo al inicio del flujo — listo para usar en SRT/VTT o programación en pantalla. |
IsFinal | Reservado para distinguir hipótesis provisionales de finales una vez que existan resultados provisionales; los segmentos actualmente siempre son finales (true). |
Language | Código ISO 639-1 detectado/usado, o null si se desconoce. |
Confidence | Confianza promedio de los tokens (0..1), o 0 cuando el modelo no reporta probabilidades de token. |
Ayudantes de subtítulos¶
SubtitleWriter — archivos SRT/VTT¶
SubtitleWriter escribe los segmentos de voz reconocidos en un archivo lateral SubRip (.srt) o WebVTT (.vtt), añadiendo una entrada por cada segmento final. Es seguro para subprocesos (thread-safe); los segmentos provisionales (no finales) se ignoran.
using VisioForge.Core.AI.Whisper.Subtitles;
using var writer = new SubtitleWriter("captions.srt", SubtitleFormat.Srt);
stt.OnSpeechRecognized += (sender, e) =>
{
foreach (var segment in e.Segments)
{
writer.Add(segment);
}
};
Si solo necesita archivos, es más sencillo establecer OutputSrtPath y/o OutputVttPath en SpeechToTextSettings — el bloque crea y gestiona internamente su(s) propia(s) instancia(s) de SubtitleWriter a medida que se reconocen los segmentos finales, y las descarta por usted.
SubtitleRenderer — subtítulos en pantalla¶
SubtitleRenderer controla una única superposición de texto en un OverlayManagerBlock: muestra el último subtítulo y lo oculta automáticamente tras la duración de visualización del segmento.
using SkiaSharp;
using VisioForge.Core.AI.Whisper.Subtitles;
var style = new SubtitleStyle
{
FontName = "Arial",
FontSize = 32,
Color = SKColors.White,
X = 50,
Y = 50,
MinDisplay = TimeSpan.FromSeconds(1.5),
MaxDisplay = TimeSpan.FromSeconds(6),
};
var subtitleRenderer = new SubtitleRenderer(overlayManagerBlock, style);
stt.OnSpeechRecognized += subtitleRenderer.OnSpeechRecognized;
// ... más tarde, al finalizar:
subtitleRenderer.Dispose(); // elimina la superposición y detiene el temporizador de ocultamiento automático
Conecte SubtitleRenderer.OnSpeechRecognized directamente como el manejador de eventos del bloque. Limita el tiempo en pantalla dentro de [MinDisplay, MaxDisplay] según la duración del segmento y llama a OverlayManagerBlock.Video_Overlay_Update desde cualquier hilo que lo invoque — despache la llamada si su framework de UI lo requiere. Valores predeterminados de SubtitleStyle: FontName = "Arial", FontSize = 32, Color = White, X = 50, Y = 50, MinDisplay = 1.5 s, MaxDisplay = 6 s.
Aún sin demo incluida
SubtitleRenderer existe en el SDK y está documentado a partir de su código fuente, pero actualmente ninguna demo incluida lo utiliza — las demos de Live Subtitles X actualizan una etiqueta de UI directamente desde OnSpeechRecognized en su lugar. Use SubtitleWriter o OutputSrtPath/OutputVttPath si solo necesita un archivo de subtítulos.
Pipeline manual de Media Blocks¶
Coloque SpeechToTextBlock en la cadena de audio antes del renderizador o la salida de audio:
var stt = new SpeechToTextBlock(settings);
stt.OnSpeechRecognized += SpeechToText_OnSpeechRecognized;
pipeline.Connect(audioSource.Output, stt.Input);
pipeline.Connect(stt.Output, audioRenderer.Input);
Transcripción de micrófono en vivo con VideoCaptureCoreX¶
Para captura, se requiere una fuente de audio. Si desea análisis sin monitoreo de altavoz ni grabación, termine la cadena de audio con un renderizador nulo no sincronizado:
core.Audio_Source = microphoneSettings;
core.Audio_OutputBlock = new NullRendererBlock(MediaBlockPadMediaType.Audio)
{
IsSync = false,
};
var stt = new SpeechToTextBlock(settings);
stt.OnSpeechRecognized += SpeechToText_OnSpeechRecognized;
core.Audio_Processing_AddBlock(stt); // antes de StartAsync
await core.StartAsync();
Audio_OutputBlock construye y termina la cadena de audio sin habilitar la reproducción por altavoz ni la grabación en archivo. El motor asume la propiedad del bloque de salida asignado tras el inicio.
Transcripción de archivo con MediaPlayerCoreX¶
Para reproducción, Audio_Play debe ser true para que se construya la cadena de audio. Un renderizador nulo no sincronizado permite que la fuente se ejecute sin salida real de altavoz:
player.Audio_Play = true;
player.Audio_OutputBlock = new NullRendererBlock(MediaBlockPadMediaType.Audio)
{
IsSync = false,
};
var stt = new SpeechToTextBlock(settings);
stt.OnSpeechRecognized += SpeechToText_OnSpeechRecognized;
player.Audio_Processing_AddBlock(stt); // antes de OpenAsync / PlayAsync
await player.OpenAsync(source);
await player.PlayAsync();
Consulte Uso de bloques de IA con VideoCaptureCoreX y MediaPlayerCoreX para la API completa de Audio_Processing_*/Audio_OutputBlock y las reglas de ciclo de vida.
Subprocesamiento, ritmo y tiempo de vida¶
VAD y Whisper se ejecutan de forma síncrona en el hilo de streaming de GStreamer: el audio se segmenta y transcribe en el lugar, y el segmento final se vuelca al final del flujo. Nada se descarta — toda la entrada se transcribe sin pérdidas, y la posición del pipeline sigue el frente de transcripción (útil para una barra de progreso). Como la transcripción se ejecuta en línea, la fuente se ajusta al ritmo de Whisper: si Whisper es más lento que el tiempo real, la fuente ascendente (incluido un dispositivo de captura en vivo) se limita en lugar de perder audio. En la práctica, Whisper Base funciona muy por encima del tiempo real, por lo que no es el cuello de botella para una fuente típica.
OnSpeechRecognized se dispara en ese mismo hilo de streaming — nunca toque la UI directamente desde el manejador; despache la llamada al hilo/dispatcher de la UI.
Después de que una sesión de captura o reproducción se inicia, el motor asume la propiedad de las instancias conectadas de SpeechToTextBlock y las descarta cuando la sesión se detiene. Cree un bloque nuevo para la siguiente sesión.
Casos de uso¶
- Subtitulado en vivo — subtítulos en tiempo real para una transmisión en vivo, webinar o superposición de accesibilidad.
- Transcripción de reuniones y llamadas — transcribe una fuente de micrófono junto con la captura de
VideoCaptureCoreX. - Indexación y búsqueda de medios — transcribe por lotes archivos de video/audio grabados para hacer su contenido buscable.
- Autoría de subtítulos — genera archivos laterales
.srt/.vtta partir del video de origen sin un servicio de transcripción de terceros. - Subtítulos de traducción — establezca
Task = SpeechToTextTask.Translatepara subtitular voz que no está en inglés, en inglés.
Solución de problemas¶
| Síntoma | Causa probable | Solución |
|---|---|---|
| Nunca se reconoce ningún segmento | WhisperModelPath no es válido, o la cadena de audio no está construida | Confirme que el archivo del modelo GGML existe en esa ruta; para captura/reproducción, confirme que la cadena de audio está activa (ver Audio_OutputBlock abajo). |
| Whisper "alucina" texto durante el silencio | EnableVad es false | Habilite VAD (EnableVad = true, el valor predeterminado) para que Whisper solo se ejecute sobre voz detectada, no sobre ventanas fijas que pueden estar en silencio. |
| La transcripción se retrasa respecto al audio en vivo | Whisper es más lento que el tiempo real en el hardware/tamaño de modelo actual | Elija un WhisperModelSize/archivo de modelo más pequeño, o use Provider = CUDA en una máquina con GPU NVIDIA. |
| Los segmentos se cortan a mitad de frase | SileroVadSettings.MaxSpeechMs corta forzosamente una locución continua larga | Este es un límite deliberado (15 s por defecto) para que una transcripción en curso no pueda crecer sin límite; aumente MaxSpeechMs si su escenario necesita segmentos ininterrumpidos más largos y puede tolerar el límite mayor. |
No llega audio al bloque en VideoCaptureCoreX/MediaPlayerCoreX | Cadena de audio no construida (falta Audio_Source/Audio_Play) | Consulte Transcripción de micrófono en vivo con VideoCaptureCoreX y Transcripción de archivo con MediaPlayerCoreX arriba. |
El archivo .srt/.vtt está vacío | Los segmentos nunca se finalizaron, o la ruta es incorrecta | Confirme que OutputSrtPath/OutputVttPath apuntan a una ruta con permisos de escritura y que realmente se detectó voz; solo se escriben los segmentos finales. |
Preguntas frecuentes¶
¿SpeechToTextBlock requiere conexión a internet?¶
No — la transcripción se ejecuta completamente en el dispositivo mediante Whisper.net/GGML; los archivos del modelo se descargan una vez desde su aplicación (o se incluyen), y no se llaman por solicitud a través de la red.
¿Qué idiomas admite?¶
Whisper es multilingüe — establezca Language con un código ISO 639-1, o déjelo en "auto" para que Whisper detecte el idioma hablado automáticamente.
¿Puedo traducir la voz a subtítulos en inglés en lugar de transcribir el idioma de origen?¶
Sí — establezca SpeechToTextSettings.Task = SpeechToTextTask.Translate.
¿Cómo obtengo subtítulos en pantalla en vivo en lugar de solo un evento?¶
Conecte SubtitleRenderer.OnSpeechRecognized como el manejador de eventos del bloque contra un OverlayManagerBlock — consulte SubtitleRenderer — subtítulos en pantalla. Si solo necesita archivos SRT/VTT, establezca OutputSrtPath/OutputVttPath en su lugar.
¿Qué tamaño de modelo Whisper debería usar?¶
Comience con Base (el predeterminado) para transcripción en tiempo real en CPU. Pase a Small/Medium/LargeV3 (o sus variantes cuantizadas) para mayor precisión si tiene una GPU o puede tolerar un procesamiento más lento que el tiempo real; consulte la tabla de Tamaños de modelo de Whisper para ver el equilibrio completo.
Demos¶
- Live Subtitles Demo — demo de pipeline de Media Blocks para WPF.
- Live Subtitles MB — la misma demo de Media Blocks para MAUI.
- Live Subtitles — demo de consola sin interfaz (descarga los modelos en la primera ejecución).
Las demos dedicadas de subtítulos en vivo de VideoCaptureCoreX/MediaPlayerCoreX (Capture Live Subtitles X, Capture Live Subtitles X WPF, Player Live Subtitles X, Player Live Subtitles X WPF) están en el conjunto de demos del SDK y se enlazarán aquí una vez publicadas en el repositorio público de ejemplos.