Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/test-rust.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ rm -rf sherpa-onnx-punct-ct-transformer-zh-en-vocab272727-2024-04-12-int8

./run-silero-vad-remove-silence.sh

./run-ten-vad-remove-silence.sh

./run-nemo-parakeet-en.sh
./run-zipformer-vi.sh
./run-zipformer-zh-en.sh
Expand Down
7 changes: 7 additions & 0 deletions rust-api-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ in your own Cargo project, see
| 46 | [zipformer_transducer_simulate_streaming_microphone](#example-46-simulated-streaming-asr-with-zipformer-transducer-japanese-and-vad-from-microphone) | Simulated streaming ASR with Zipformer transducer (Japanese) and VAD from microphone |
| 47 | [qwen3_asr_simulate_streaming_microphone](#example-47-simulated-streaming-asr-with-qwen3-asr-and-vad-from-microphone) | Simulated streaming ASR with Qwen3 ASR and VAD from microphone |
| 48 | [whisper](#example-48-asr-with-non-streaming-whisper) | Non-streaming ASR with Whisper (multilingual) |
| 49 | [ten_vad_remove_silence](#example-49-remove-silences-from-a-file-using-ten-vad) | Remove silences from an audio file using ten-vad |

## Run it

Expand Down Expand Up @@ -422,3 +423,9 @@ Qwen3 ASR recognizer on each detected segment.
```bash
./run-whisper.sh
```

### Example 49: Remove silences from a file using ten-vad

```bash
./run-ten-vad-remove-silence.sh
```
122 changes: 122 additions & 0 deletions rust-api-examples/examples/ten_vad_remove_silence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// This file demonstrates how to use ten-vad with sherpa-onnx's
// Rust API to remove non-speech segments and save speech-only audio.
//
// See ../README.md for how to run it

use clap::Parser;
use sherpa_onnx::{self, TenVadModelConfig, VadModelConfig, VoiceActivityDetector, Wave};

/// Simple VAD example: remove non-speech segments from a WAV file using ten-vad
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Path to input WAV file
#[arg(long)]
input: String,

/// Path to output WAV file
#[arg(long)]
output: String,

/// Path to ten-vad ONNX model
#[arg(long)]
ten_vad_model: String,
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();

// Read WAV file
let wave = Wave::read(&args.input)
.ok_or_else(|| anyhow::anyhow!("Failed to read WAV file: {}", &args.input))?;
let sample_rate = wave.sample_rate();
let input_num_samples = wave.num_samples();
let input_duration = input_num_samples as f32 / sample_rate as f32;

println!(
"Input WAV: sample rate: {}, num samples: {}, duration: {:.2}s",
sample_rate, input_num_samples, input_duration
);
Comment on lines +38 to +41

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since ten-vad strictly requires a sample rate of 16000 Hz (and the underlying C++ implementation will abruptly terminate the process with exit(-1) if it is not), it is highly recommended to validate the sample rate in Rust and return a graceful error.

Suggested change
println!(
"Input WAV: sample rate: {}, num samples: {}, duration: {:.2}s",
sample_rate, input_num_samples, input_duration
);
println!(
"Input WAV: sample rate: {}, num samples: {}, duration: {:.2}s",
sample_rate, input_num_samples, input_duration
);
if sample_rate != 16000 {
anyhow::bail!(
"ten-vad expects a sample rate of 16000 Hz, but the input file has {} Hz",
sample_rate
);
}


if sample_rate != 16000 {
anyhow::bail!(
"ten-vad expects a sample rate of 16000 Hz, but the input file has {} Hz",
sample_rate
);
}

// Configure VAD
let mut ten_vad_config = TenVadModelConfig::default();
ten_vad_config.model = Some(args.ten_vad_model);

// ten-vad expects a window size of 256 samples at 16 kHz.
// Please don't change it unless you know what you are doing.
ten_vad_config.window_size = 256;

// You can tune the values below
ten_vad_config.threshold = 0.5;
ten_vad_config.min_silence_duration = 0.25;
ten_vad_config.min_speech_duration = 0.5;
ten_vad_config.max_speech_duration = 5.0;

let window_size = ten_vad_config.window_size as usize;

let vad_config = VadModelConfig {
silero_vad: Default::default(),
ten_vad: ten_vad_config,
sample_rate,
num_threads: 1,
provider: Some("cpu".to_string()),
debug: false,
};

let vad = VoiceActivityDetector::create(&vad_config, 30.0)
.ok_or_else(|| anyhow::anyhow!("Failed to create VoiceActivityDetector"))?;

let mut speech_samples = Vec::with_capacity(input_num_samples as usize);

for chunk in wave.samples().chunks(window_size) {
vad.accept_waveform(chunk);

while let Some(seg) = vad.front() {
speech_samples.extend_from_slice(seg.samples());
vad.pop();
}
}

vad.flush();
while let Some(seg) = vad.front() {
speech_samples.extend_from_slice(seg.samples());
vad.pop();
}

// Write speech-only samples to output WAV
let ok = sherpa_onnx::write(&args.output, &speech_samples, sample_rate);
if !ok {
anyhow::bail!("Failed to save speech-only audio to {}", args.output);
}
println!("Saved speech-only audio to {}", args.output);

// Summary
let output_num_samples = speech_samples.len();
let output_duration = output_num_samples as f32 / sample_rate as f32;
println!("\n=== Summary ===");
println!(
"Input: sample rate = {}, samples = {}, duration = {:.2}s",
sample_rate, input_num_samples, input_duration
);
println!(
"Output: sample rate = {}, samples = {}, duration = {:.2}s",
sample_rate, output_num_samples, output_duration
);
if input_duration > 0.0 {
println!(
"Removed non-speech: {:.2}% of input removed",
100.0 * (1.0 - output_duration / input_duration)
);
}

Ok(())
}
16 changes: 16 additions & 0 deletions rust-api-examples/run-ten-vad-remove-silence.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -ex

# https://k2-fsa.github.io/sherpa/onnx/vad/index.html
if [ ! -f "./ten-vad.onnx" ]; then
curl -SLf -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/ten-vad.onnx
fi

if [ ! -f ./lei-jun-test.wav ]; then
curl -SLf -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi
Comment on lines +5 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

By default, curl does not return a non-zero exit code on HTTP errors (like 404 or 500) unless -f or --fail is specified. Adding -f ensures the script fails immediately if the download fails, preventing subsequent commands from trying to use invalid/empty files.

Suggested change
if [ ! -f "./ten-vad.onnx" ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/ten-vad.onnx
fi
if [ ! -f ./lei-jun-test.wav ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi
if [ ! -f "./ten-vad.onnx" ]; then
curl -SLf -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/ten-vad.onnx
fi
if [ ! -f ./lei-jun-test.wav ]; then
curl -SLf -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi


cargo run --example ten_vad_remove_silence -- \
--input ./lei-jun-test.wav \
--output ./no-silence-ten-vad.wav \
--ten-vad-model ./ten-vad.onnx