diff --git a/.github/scripts/test-rust.sh b/.github/scripts/test-rust.sh index 1a589ec190..adbd99b4a6 100755 --- a/.github/scripts/test-rust.sh +++ b/.github/scripts/test-rust.sh @@ -67,6 +67,7 @@ rm -rf sherpa-onnx-online-punct-* rm -rf sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01-mobile ./run-spoken-language-identification.sh +./run-whisper.sh rm -rf sherpa-onnx-whisper-tiny spoken-language-identification-test-wavs ./run-offline-punctuation.sh diff --git a/rust-api-examples/README.md b/rust-api-examples/README.md index 2141a1fee8..4e51d9fe9c 100644 --- a/rust-api-examples/README.md +++ b/rust-api-examples/README.md @@ -90,6 +90,7 @@ in your own Cargo project, see | 45 | [zipformer_transducer_simulate_streaming_microphone](#example-45-simulated-streaming-asr-with-zipformer-transducer-and-vad-from-microphone) | Simulated streaming ASR with Zipformer transducer and VAD from microphone | | 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) | ## Run it @@ -415,3 +416,9 @@ Qwen3 ASR recognizer on each detected segment. ```bash ./run-qwen3-asr-simulate-streaming-microphone.sh ``` + +### Example 48: ASR with non-streaming Whisper + +```bash +./run-whisper.sh +``` diff --git a/rust-api-examples/examples/whisper.rs b/rust-api-examples/examples/whisper.rs new file mode 100644 index 0000000000..ebd7504a0b --- /dev/null +++ b/rust-api-examples/examples/whisper.rs @@ -0,0 +1,119 @@ +// Copyright (c) 2026 Yujie Wu +// +// This file demonstrates how to use Whisper with sherpa-onnx's Rust API +// for offline speech recognition. +// +// See ../README.md for how to run it. + +use clap::Parser; +use sherpa_onnx::{OfflineRecognizer, OfflineRecognizerConfig, OfflineWhisperModelConfig, Wave}; +use std::time::Instant; + +/// Whisper offline ASR example +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Path to encoder ONNX model + #[arg(long)] + encoder: String, + + /// Path to decoder ONNX model + #[arg(long)] + decoder: String, + + /// Path to tokens file + #[arg(long)] + tokens: String, + + /// Path to input WAV file + #[arg(long)] + wav: String, + + /// Recognition language, e.g. "en", "zh". Leave empty for auto-detection + #[arg(long, default_value = "")] + language: String, + + /// Task type: "transcribe" or "translate" (translate to English) + #[arg(long, default_value = "transcribe")] + task: String, + + /// Provider (default: cpu) + #[arg(long, default_value = "cpu")] + provider: String, + + /// Enable debug logs + #[arg(long, default_value_t = false)] + debug: bool, + + /// Number of threads + #[arg(long, default_value_t = 2)] + num_threads: i32, +} + +fn main() { + let args = Args::parse(); + + let wave = Wave::read(&args.wav).expect("Failed to read WAV file"); + let audio_duration = wave.samples().len() as f64 / wave.sample_rate() as f64; + + // Create default recognizer config + let mut recognizer_config = OfflineRecognizerConfig::default(); + + // Set the Whisper model + recognizer_config.model_config.whisper = OfflineWhisperModelConfig { + encoder: Some(args.encoder.clone()), + decoder: Some(args.decoder.clone()), + language: Some(args.language.clone()), + task: Some(args.task.clone()), + tail_paddings: 0, + enable_token_timestamps: false, + enable_segment_timestamps: false, + }; + + recognizer_config.model_config.tokens = Some(args.tokens.clone()); + recognizer_config.model_config.provider = Some(args.provider.clone()); + recognizer_config.model_config.debug = args.debug; + recognizer_config.model_config.num_threads = args.num_threads; + + // Measure recognizer creation time + println!("Creating recognizer ..."); + let start_creation = Instant::now(); + let recognizer = + OfflineRecognizer::create(&recognizer_config).expect("Failed to create OfflineRecognizer"); + let creation_elapsed = start_creation.elapsed().as_secs_f64(); + println!("Recognizer created in {:.3} seconds.", creation_elapsed); + + let stream = recognizer.create_stream(); + + // Measure recognition time + let start_recognition = Instant::now(); + stream.accept_waveform(wave.sample_rate(), wave.samples()); + recognizer.decode(&stream); + let recognition_elapsed = start_recognition.elapsed().as_secs_f64(); + + // Get recognition result + if let Some(result) = stream.get_result() { + println!("Decoded text: {}", result.text); + + let total_elapsed = creation_elapsed + recognition_elapsed; + let rtf = recognition_elapsed / audio_duration; + println!("\n=== Performance Summary ==="); + println!("Audio duration : {:.3} seconds", audio_duration); + println!("Recognizer creation time: {:.3} seconds", creation_elapsed); + println!( + "Recognition time : {:.3} seconds", + recognition_elapsed + ); + println!("Total elapsed time : {:.3} seconds", total_elapsed); + println!( + "Real-Time Factor (RTF) : {:.3} (recognition_elapsed / audio_duration = {:.3} / {:.3})", + rtf, recognition_elapsed, audio_duration + ); + println!( + "Number of threads : {}", + recognizer_config.model_config.num_threads + ); + } else { + eprintln!("Failed to get recognition result"); + } +} diff --git a/rust-api-examples/run-whisper.sh b/rust-api-examples/run-whisper.sh new file mode 100755 index 0000000000..368eaab859 --- /dev/null +++ b/rust-api-examples/run-whisper.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -ex + +# see +# https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models +if [ ! -f ./sherpa-onnx-whisper-tiny/tiny-encoder.onnx ]; then + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-whisper-tiny.tar.bz2 + tar xvf sherpa-onnx-whisper-tiny.tar.bz2 + rm sherpa-onnx-whisper-tiny.tar.bz2 +fi + +cargo run --example whisper -- \ + --wav ./sherpa-onnx-whisper-tiny/test_wavs/0.wav \ + --encoder ./sherpa-onnx-whisper-tiny/tiny-encoder.onnx \ + --decoder ./sherpa-onnx-whisper-tiny/tiny-decoder.onnx \ + --tokens ./sherpa-onnx-whisper-tiny/tiny-tokens.txt \ + --language en \ + --num-threads 2