VoskSpeechRecognizer.cs

using System.Text.Json;
using System.Text.Json.Serialization;
using NAudio.CoreAudioApi;
using NAudio.Wave;
using STTTS.Common.Audio;
using STTTS.Common.Configuration;
using Vosk;

namespace STTTS.Engine.STT.Recognizers;

public class VoskSpeechRecognizer : BaseSpeechRecognizer
{
	private Model? _model;
	private VoskRecognizer? _recognizer;
	private IWaveIn? _waveInEvent;

	public VoskSpeechRecognizer() : base()
	{
		AttachEventHandlers();
	}

	private void AttachEventHandlers()
	{
		ConfigurationState.Instance.Audio.InputDeviceID.ValueChanged += DeviceChanged;
		ConfigurationState.Instance.Audio.OutputDeviceID.ValueChanged += DeviceChanged;
	}

	public override bool Start()
	{
		string path = ConfigurationState.Instance.Vosk.ModelDirectory.Value;
		if (string.IsNullOrEmpty(path))
		{
			return false;
		}

		// only if we are not paused do we need to initialize everything
		if (!Paused)
		{
			try
			{
				_model = new Model(path);
				_recognizer = new VoskRecognizer(_model, 48000f);
			}
			catch (Exception _)
			{
				return false;
			}

			var device = AudioDevices.GetDeviceByID(ConfigurationState.Instance.Audio.InputDeviceID.Value);
			_waveInEvent = new WasapiCapture(device)
			{
				ShareMode = AudioClientShareMode.Shared,
				WaveFormat = new WaveFormat(48000, 16, 1),
			};
			_waveInEvent.DataAvailable += OnDataAvailable;
		}

		// start the recording of device input
		_waveInEvent!.StartRecording();

		Paused = false;
		Stopped = false;
		OnStateChanged();
		return true;
	}

	private void OnDataAvailable(object? sender, WaveInEventArgs e)
	{
		// do nothing if we are paused
		if (Paused)
		{
			return;
		}

		if (_recognizer!.AcceptWaveform(e.Buffer, e.BytesRecorded))
		{
			string json = _recognizer.Result();
			var result = JsonSerializer.Deserialize<VoskResult>(json);
			if (result != null && !string.IsNullOrEmpty(result.Text))
			{
				OnRecognizedSpeech(result.Text);
			}
		}
	}

	public override void Pause()
	{
		if (!Paused && !Stopped)
		{
			_waveInEvent!.StopRecording();
			Paused = true;
			OnStateChanged();
		}
	}

	public override void Stop()
	{
		if (!Stopped)
		{
			if (_waveInEvent != null)
			{
				_waveInEvent.StopRecording();
				_waveInEvent.DataAvailable -= OnDataAvailable;
				_waveInEvent.Dispose();
				_waveInEvent = null;
			}

			if (_recognizer != null)
			{
				_recognizer.Dispose();
				_recognizer = null;
			}

			if (_model != null)
			{
				_model.Dispose();
				_model = null;
			}
			
			Paused = false;
			Stopped = true;
			OnStateChanged();
		}
	}

	public override void Dispose()
	{
		if (!Stopped)
		{
			Stop();
		}
	}
}

public class VoskResultWord
{
	[JsonPropertyName("conf")]
	public float Conf { get; set; }
	[JsonPropertyName("end")]
	public float End { get; set; }
	[JsonPropertyName("start")]
	public float Start { get; set; }
	[JsonPropertyName("word")]
	public string Word { get; set; } = string.Empty;
}

public class VoskResult
{
	[JsonPropertyName("result")]
	public List<VoskResultWord> Result { get; set; } = new List<VoskResultWord>();
	[JsonPropertyName("text")]
	public string Text { get; set; } = string.Empty;
}

BaseSpeechRecognizer.cs
using STTTS.Common.Configuration;
using STTTS.Common.Events;

namespace STTTS.Engine.STT.Recognizers;

public abstract class BaseSpeechRecognizer : IDisposable
{
	public event EventHandler<ErrorEventArgs>? ErrorTriggered;
	public event EventHandler<EventArgs>? StateChanged;
	public event EventHandler<RecognizedSpeechEventArgs>? RecognizedSpeech;

	/// <summary>
	/// Specifies if the Recognizer is currently listening
	/// to the Input device.
	/// </summary>
	public bool Paused { get; protected set; }

	/// <summary>
	/// Specifies if the Recognizer is currently initialized
	/// and can begin listening to the current Input device.
	/// </summary>
	public bool Stopped { get; protected set; }

	public BaseSpeechRecognizer()
	{
		Paused = false;
		Stopped = true;
		ConfigurationState.Instance.Audio.InputDeviceID.ValueChanged += DeviceChanged;
		ConfigurationState.Instance.Audio.OutputDeviceID.ValueChanged += DeviceChanged;
	}

	/// <summary>
	/// Creates the model and begins listening to the selected
	/// Input device for audio samples to be translated.
	/// </summary>
	/// <returns>If the recogizer has started or not</returns>
	public abstract bool Start();
	public bool CanStart => Paused || Stopped;

	/// <summary>
	/// Stops listening to the Input device for audio samples
	/// without removing the underlying model.
	/// </summary>
	public abstract void Pause();
	public bool CanPause => !Paused && !Stopped;

	/// <summary>
	/// Stops listening to the Input device for audio samples
	/// and removes the underlying model.
	/// </summary>
	public abstract void Stop();
	public bool CanStop => !Stopped;

	protected void OnStateChanged() =>
		StateChanged?.Invoke(this, EventArgs.Empty);

	protected void OnRecognizedSpeech(string text) =>
		RecognizedSpeech?.Invoke(this, new RecognizedSpeechEventArgs(text));

	/// <summary>
	/// Called when an error occurs within the Recognizer,
	/// calls the ErrorTriggered event for listeners to capture.
	/// </summary>
	protected void OnError(Exception e) =>
		ErrorTriggered?.Invoke(this, new ErrorEventArgs(e));

	/// <summary>
	/// Method to attach to ReactiveObjects to detect when a device has changed
	/// from the Configuration State.
	/// We want to avoid having the used Device change mid conversion,
	/// so detect when it changes to stop the Recognizer.
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	protected void DeviceChanged(object? sender, ValueChangedEventArgs<string> e) =>
		Stop();

	public abstract void Dispose();
}


как работает весь этот код