feat: load vad model (#247)

---------

Co-authored-by: Neko Ayaka <neko@ayaka.moe>
This commit is contained in:
RainbowBird
2025-06-29 23:52:30 +08:00
committed by GitHub
co-authored by Neko Ayaka
parent 379a495b14
commit 06602b59f7
5 changed files with 144 additions and 19 deletions
+3 -19
View File
@@ -97,28 +97,12 @@ async fn stop_click_through(window: tauri::Window) -> Result<(), String> {
Ok(())
}
fn load_whisper_model(window: tauri::Window) -> anyhow::Result<()> {
let progress_manager = crate::whisper::progress::ModelLoadProgressEmitterManager::new(window);
// Determine device to use
let device = if candle_core::utils::cuda_is_available() {
candle_core::Device::new_cuda(0)?
} else if candle_core::utils::metal_is_available() {
candle_core::Device::new_metal(0)?
} else {
candle_core::Device::Cpu
};
let whisper_model = whisper::whisper::WhichWhisperModel::Tiny;
let _ = whisper::whisper::WhisperProcessor::new(whisper_model, device.clone(), progress_manager)?;
Ok(())
}
#[tauri::command]
async fn load_models(window: tauri::Window) -> Result<(), String> {
let _ = load_whisper_model(window);
let device = whisper::model_manager::load_device().unwrap();
whisper::model_manager::load_whisper_model(window.clone(), device.clone()).unwrap();
whisper::model_manager::load_vad_model(window.clone(), candle_core::Device::Cpu).unwrap();
Ok(())
}
@@ -1,2 +1,4 @@
pub mod progress;
pub mod whisper;
pub mod model_manager;
pub mod vad;
@@ -0,0 +1,43 @@
use log::info;
use anyhow::Ok;
use crate::whisper::whisper::{WhichWhisperModel, WhisperProcessor};
use crate::whisper::progress::ModelLoadProgressEmitterManager;
use crate::whisper::vad::VADProcessor;
pub fn load_device() -> anyhow::Result<candle_core::Device> {
// Determine device to use
let device = if candle_core::utils::cuda_is_available() {
candle_core::Device::new_cuda(0)?
} else if candle_core::utils::metal_is_available() {
candle_core::Device::new_metal(0)?
} else {
candle_core::Device::Cpu
};
info!("Using device: {device:?}");
Ok(device)
}
pub fn load_whisper_model(window: tauri::Window, device: candle_core::Device) -> anyhow::Result<()> {
let progress_manager = ModelLoadProgressEmitterManager::new(window);
let whisper_model = WhichWhisperModel::Tiny;
info!("Loading whisper model: {:?}", whisper_model);
let _ = WhisperProcessor::new(whisper_model, device.clone(), progress_manager)?;
Ok(())
}
pub fn load_vad_model(window: tauri::Window, device: candle_core::Device) -> anyhow::Result<()> {
let progress_manager = ModelLoadProgressEmitterManager::new(window);
let whisper_model = WhichWhisperModel::Tiny;
info!("Loading VAD model: {:?}", whisper_model);
let _ = VADProcessor::new(device.clone(), 0.3, progress_manager)?;
Ok(())
}
@@ -0,0 +1,96 @@
use std::collections::HashMap;
use anyhow::Result;
use candle_core::{DType, Device, Tensor};
use candle_onnx::simple_eval;
use hf_hub::{Repo, RepoType};
use crate::whisper::progress;
pub struct VADProcessor {
model: candle_onnx::onnx::ModelProto,
frame_size: usize,
context_size: usize,
sample_rate: Tensor,
state: Tensor,
context: Tensor,
device: Device,
threshold: f32,
}
impl VADProcessor {
pub fn new(
device: Device,
threshold: f32,
manager: progress::ModelLoadProgressEmitterManager,
) -> Result<Self> {
let api = hf_hub::api::sync::Api::new()?;
let repo = api.repo(Repo::with_revision(
"onnx-community/silero-vad".into(),
RepoType::Model,
"main".into(),
));
let model_path = repo.download_with_progress(
"onnx/model.onnx",
manager.clone().new_for("onnx/model.onnx"),
)?;
let model = candle_onnx::read_file(model_path)?;
let sample_rate_value = 16000i64;
let (frame_size, context_size) = (512, 64);
Ok(Self {
model,
frame_size,
context_size,
sample_rate: Tensor::new(sample_rate_value, &device)?,
state: Tensor::zeros((2, 1, 128), DType::F32, &device)?,
context: Tensor::zeros((1, context_size), DType::F32, &device)?,
device,
threshold,
})
}
pub fn process_chunk(
&mut self,
chunk: &[f32],
) -> Result<f32> {
if chunk.len() != self.frame_size {
return Ok(0.0);
}
let next_context = Tensor::from_slice(&chunk[self.frame_size - self.context_size..], (1, self.context_size), &self.device)?;
let chunk_tensor = Tensor::from_vec(chunk.to_vec(), (1, self.frame_size), &self.device)?;
let input = Tensor::cat(&[&self.context, &chunk_tensor], 1)?;
let inputs: HashMap<String, Tensor> = HashMap::from_iter([("input".to_string(), input), ("sr".to_string(), self.sample_rate.clone()), ("state".to_string(), self.state.clone())]);
let outputs = simple_eval(&self.model, inputs)?;
let graph = self.model.graph.as_ref().unwrap();
let out_names = &graph.output;
let output = outputs
.get(&out_names[0].name)
.ok_or_else(|| anyhow::anyhow!("Missing VAD output tensor: {}", &out_names[0].name))?
.clone();
self.state = outputs
.get(&out_names[1].name)
.ok_or_else(|| anyhow::anyhow!("Missing VAD state tensor: {}", &out_names[1].name))?
.clone();
self.context = next_context;
let speech_prob = output.flatten_all()?.to_vec1::<f32>()?[0];
Ok(speech_prob)
}
pub fn is_speech(
&self,
prob: f32,
) -> bool {
prob >= self.threshold
}
}