Audio input requirements for transcribe-rs
mainAll audio input provided to the transcription engines must adhere to the following format:
- Sample Rate: 16 kHz
- Channels: Mono
- Format: 16-bit PCM WAV
repository·main·Indexed 42 days ago
https://github.com/cjpais/transcribe-rsA multi-engine speech-to-text library for Rust providing a unified interface for local engines (ONNX, whisper.cpp, Whisperfile) and remote engines (OpenAI). Version 0.3.11 supports models such as Parakeet, Canary, Cohere, Moonshine, SenseVoice, and GigaAM, with optional GPU acceleration via CUDA, Metal, Vulkan, ROCm, DirectML, and CoreML.
All audio input provided to the transcription engines must adhere to the following format:
By default, engines use the CPU. You can enable GPU acceleration by enabling the appropriate feature flag at compile time and setting the accelerator preference at runtime before loading any models.
Use set_ort_accelerator with OrtAccelerator values. Note that OrtAccelerator::Auto does not include DirectML due to specific session requirements; you must select OrtAccelerator::DirectMl explicitly if using DirectML.
OrtAccelerator::Cuda: NVIDIA CUDAOrtAccelerator::Auto: Auto-detect best available GPUOrtAccelerator::DirectMl: Microsoft DirectML (Windows)OrtAccelerator::available(): Query which accelerators are compiled in.GPU backends (Metal, Vulkan, CUDA) are selected at compile time via feature flags. Use set_whisper_accelerator to control usage at runtime.
WhisperAccelerator::CpuOnly: Force CPU usage.use transcribe_rs::{set_ort_accelerator, OrtAccelerator};
// Use CUDA for all ORT engines (SenseVoice, GigaAM, Parakeet, Moonshine)
set_ort_accelerator(OrtAccelerator::Cuda);
// Or auto-detect the best available GPU
set_ort_accelerator(OrtAccelerator::Auto);use transcribe_rs::{set_whisper_accelerator, WhisperAccelerator};
set_whisper_accelerator(WhisperAccelerator::CpuOnly); // force CPUUse one of two patterns depending on model loading cost:
For models that load quickly (e.g., most ONNX models), load a new instance per test. Always use common::require_paths() to skip tests gracefully if model files are missing.
// tests/your_model.rs
mod common;
use std::path::PathBuf;
use transcribe_rs::your_engine::your_model::YourModel;
use transcribe_rs::SpeechModel;
#[test]
fn test_your_model_transcribe() {
env_logger::init();
let model_dir = PathBuf::from("models/your-model");
let wav_path = PathBuf::from("samples/jfk.wav");
if !common::require_paths(&[&model_dir, &wav_path]) {
return;
}
let mut model = YourModel::load(&model_dir).expect("Failed to load model");
let result = model
.transcribe_file(&wav_path, None)
.expect("Failed to transcribe");
assert!(!result.text.is_empty(), "Transcription should not be empty");
}For slow-loading models (e.g., whisper.cpp), use a shared instance via once_cell::sync::Lazy and std::sync::Mutex to avoid reloading the model for every test.
// tests/your_model.rs
mod common;
use once_cell::sync::Lazy;
use std::path::PathBuf;
use std::sync::Mutex;
use transcribe_rs::your_engine::YourEngine;
use transcribe_rs::SpeechModel;
static ENGINE: Lazy<Mutex<Option<YourEngine>>> = Lazy::new(|| {
let model = PathBuf::from("models/your-model.bin");
if !common::require_paths(&[&model]) {
return Mutex::new(None);
}
match YourEngine::load(&model) {
Ok(engine) => Mutex::new(Some(engine)),
Err(_) => Mutex::new(None),
}
});
#[test]
fn test_transcription() {
let mut guard = match ENGINE.lock().unwrap().as_mut() {
Some(e) => e,
None => return,
};
let audio_path = PathBuf::from("samples/jfk.wav");
let result = guard.transcribe_file(&audio_path, None).expect("Failed");
assert!(!result.text.is_empty());
}Register tests in Cargo.toml with the required feature:
[[test]]
name = "your_model"
required-features = ["your-engine"]A new engine family is required when integrating a new inference runtime (e.g., Candle, Burn, MLX, TensorRT). Follow these steps:
Create src/your_engine/ with a mod.rs for engine-level types and re-exports, and subdirectories for individual models (e.g., src/your_engine/your_model/mod.rs). For simple engines, a single file src/your_engine.rs is sufficient.
Add a feature flag for your engine and include it in the all feature. If your engine requires shared audio features (like mel spectrograms), include the audio-features feature.
[features]
your-engine = ["dep:your-runtime-crate"]
# Update the "all" feature
all = ["onnx", "whisper-cpp", "whisperfile", "openai", "your-engine"]
[dependencies]
your-runtime-crate = { version = "...", optional = true }Note: To include audio features: your-engine = ["audio-features", "dep:your-runtime-crate"]
In src/lib.rs, use a conditional compilation guard:
#[cfg(feature = "your-engine")]
pub mod your_engine;Every local model must implement the SpeechModel trait. Follow these conventions:
const CAPABILITIES: ModelCapabilities.Model::load(...) as a single-step constructor.{Model}Params struct with #[derive(Debug, Clone, Default)].transcribe_with(&mut self, samples, &Params) for engine-specific parameters, and the standard transcribe() for the generic interface.TranscribeError variants like ModelNotFound, Inference, Audio, or Config.If your runtime has a custom error type, implement From in src/error.rs:
#[cfg(feature = "your-engine")]
impl From<your_runtime::Error> for TranscribeError {
fn from(e: your_runtime::Error) -> Self {
TranscribeError::Inference(e.to_string())
}
}Create src/your_engine/PORTING.md to document how to add models to this specific engine family.
Tests are feature-gated. Note that most tests require models to be present locally; tests will skip gracefully if models are not found.
cargo test --features onnx
cargo test --features whisper-cpp
cargo test --features whisperfile
cargo test --all-featuresWhisperfile Configuration:
models/whisperfile-0.9.3. You can override this using the WHISPERFILE_BIN environment variable.models/ggml-small.bin. You can override this using the WHISPERFILE_MODEL environment variable.GigaAM Requirement:
GigaAM tests require the file samples/russian.wav to be present.
cargo test --all-featuresTo add a new speech recognition model to the onnx engine family, follow these steps:
src/onnx/ (e.g., src/onnx/your_model/) and add a mod.rs file.pub mod your_model; to src/onnx/mod.rs.SpeechModel trait and provide a load method. Use src/onnx/gigaam/mod.rs as a simple reference.tests/your_model.rs and register it in Cargo.toml with required-features = ["onnx"].examples/your_model.rs and register it in Cargo.toml with required-features = ["onnx"]..onnx files and vocabulary files in a directory (e.g., models/your-model/).mkdir src/onnx/your_model
touch src/onnx/your_model/mod.rsTo perform transcription using the Parakeet model, load the model with a specific quantization (e.g., Int8), read your audio samples using transcribe_rs::audio::read_wav_samples, and call transcribe_with using ParakeetParams.
use transcribe_rs::onnx::parakeet::{ParakeetModel, ParakeetParams, TimestampGranularity};
use transcribe_rs::onnx::Quantization;
use std::path::PathBuf;
let mut model = ParakeetModel::load(
&PathBuf::from("models/parakeet-tdt-0.6b-v3-int8"),
&Quantization::Int8,
)?;
let samples = transcribe_rs::audio::read_wav_samples(&PathBuf::from("audio.wav"))?;
let result = model.transcribe_with(
&samples,
&ParakeetParams {
timestamp_granularity: Some(TimestampGranularity::Segment),
..Default::default()
},
)?;
println!("{}", result.text);When implementing a new model, follow this structure in src/onnx/your_model/mod.rs. The model must define its ModelCapabilities, a Params struct for model-specific settings, and implement the SpeechModel trait.
Key requirements:
pub fn load(model_dir: &Path, quantization: &Quantization) -> Result<Self, TranscribeError>.const CAPABILITIES: ModelCapabilities containing the model's name, engine ID, sample rate, and supported languages.infer method to handle feature extraction, tensor preparation, the ONNX forward pass, and decoding.SpeechModel to allow generic usage via the transcribe method.use ort::inputs;
use ort::session::Session;
use ort::value::TensorRef;
use std::path::Path;
use crate::decode::{ctc_greedy_decode, sentencepiece_to_text};
use crate::decode::tokens::load_vocab;
use crate::features::{compute_mel, MelConfig, WindowType};
use crate::TranscribeError;
use super::session;
use super::Quantization;
use crate::{ModelCapabilities, SpeechModel, TranscriptionResult};
const CAPABILITIES: ModelCapabilities = ModelCapabilities {
name: "Your Model", // Human-readable name
engine_id: "your_model", // Machine identifier, matches directory name
sample_rate: 16000, // Expected input sample rate
languages: &["en"],
supports_timestamps: false,
supports_translation: false,
supports_streaming: false,
};
#[derive(Debug, Clone, Default)]
pub struct YourModelParams {
pub language: Option<String>,
}
pub struct YourModel {
session: Session,
}
impl YourModel {
pub fn load(model_dir: &Path, quantization: &Quantization) -> Result<Self, TranscribeError> {
let model_path = session::resolve_model_path(model_dir, "model", quantization);
if !model_path.exists() {
return Err(TranscribeError::ModelNotFound(model_path));
}
let session = session::create_session(&model_path)?;
Ok(Self { session })
}
pub fn transcribe_with(
&mut self,
samples: &[f32],
params: &YourModelParams
) -> Result<TranscriptionResult, TranscribeError> {
self.infer(samples)
}
fn infer(&mut self, samples: &[f32]) -> Result<TranscriptionResult, TranscribeError> {
// 1. Feature extraction
// 2. Prepare input tensors
// 3. Run ONNX forward pass: self.session.run(inputs)?
// 4. Decode output
// 5. Return TranscriptionResult
todo!()
}
}
impl SpeechModel for YourModel {
fn capabilities(&self) -> ModelCapabilities {
CAPABILITIES
}
fn transcribe(
&mut self,
samples: &[f32],
_options: &TranscribeOptions
) -> Result<TranscriptionResult, TranscribeError> {
self.infer(samples)
}
}Models must be downloaded and placed in your local directory before use. The required directory structure depends on the engine.
| Engine | Download Source |
|---|---|
| Parakeet (int8) | blob.handy.computer / HuggingFace |
| Canary 180M Flash | HuggingFace |
| Canary 1B Flash | HuggingFace |
| Canary 1B v2 | HuggingFace |
| Cohere (int4) | HuggingFace |
| Cohere (int8) | HuggingFace |
| SenseVoice (int8) | blob.handy.computer / sherpa-onnx |
| Moonshine | blob.handy.computer (base), blob.handy.computer (tiny streaming en), blob.handy.computer (small streaming en), blob.handy.computer (medium streaming en) |
| GigaAM | HuggingFace |
| Whisper (GGML) | HuggingFace |
| Whisperfile binary | GitHub |
If you are adding a model that uses an existing inference runtime (like ONNX), follow these steps:
src/onnx/your_model/mod.rs (or the relevant engine directory).src/onnx/mod.rs).SpeechModel trait for your model.tests/, an example in examples/, and register them in Cargo.toml.For detailed ONNX-specific instructions, refer to src/onnx/PORTING.md.
Add transcribe-rs to your Cargo.toml. Note that no features are enabled by default; you must explicitly enable the engines you intend to use via feature flags.
| Feature | Engines |
|---|---|
onnx | Parakeet, Canary, Cohere, Moonshine, SenseVoice, GigaAM (via ONNX Runtime) |
whisper-cpp | Whisper (local, GGML via whisper.cpp with Metal/Vulkan/CUDA) |
whisperfile | Whisperfile (local server wrapper) |
openai | OpenAI API (remote, async) |
all | Everything above |
| Feature | Backend |
|---|---|
whisper-metal | Apple Metal (macOS) |
whisper-vulkan | Vulkan (Windows/Linux) |
whisper-cuda | NVIDIA CUDA (requires CUDA Toolkit 12.0+) |
| Feature | Backend |
|---|---|
ort-cuda | NVIDIA CUDA |
ort-rocm | AMD ROCm |
ort-directml | Microsoft DirectML (Windows) |
ort-coreml | Apple CoreML (macOS/iOS) |
ort-webgpu | WebGPU via Dawn |
[dependencies]
transcribe-rs = { version = "0.3", features = ["onnx"] }Every model should include an example demonstrating loading and transcription. Use transcribe_with() to show how to pass model-specific parameters.
Conventions:
Cargo.toml with required-features.// examples/your_model.rs
use std::path::PathBuf;
use std::time::Instant;
use transcribe_rs::your_engine::your_model::{YourModel, YourModelParams};
use transcribe_rs::SpeechModel;
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let model_path = PathBuf::from("models/your-model");
let wav_path = PathBuf::from("samples/jfk.wav");
// Load
let mut model = YourModel::load(&model_path)?;
// Transcribe
let samples = transcribe_rs::audio::read_wav_samples(&wav_path)?;
let result = model.transcribe_with(
&samples,
&YourModelParams {
language: Some("en".to_string()),
..Default::default()
},
)?;
println!("Transcription result: {}", result.text);
Ok(())
}Registration in Cargo.toml:
[[example]]
name = "your_model"
required-features = ["your-engine"]