use std::sync::{Arc, atomic::AtomicBool};
use crate::{
core::{AsrParams, AsrResult},
types::{ChunkId, WorkFailure},
};
#[cfg(feature = "runner")]
use crate::types::{AsrError, AsrFailure};
#[cfg(feature = "runner")]
use smol_str::format_smolstr;
#[cfg(feature = "runner")]
use whispercpp::{Context as WhisperContext, State as WhisperState};
pub trait AsrSource: Send {
fn run_chunk(&mut self, chunk: AsrChunkContext<'_>) -> Result<AsrResult, WorkFailure>;
}
pub struct AsrChunkContext<'a> {
samples: &'a [f32],
params: &'a AsrParams,
abort_flag: &'a Arc<AtomicBool>,
chunk_id: ChunkId,
}
impl<'a> AsrChunkContext<'a> {
#[must_use]
pub const fn new(
samples: &'a [f32],
params: &'a AsrParams,
abort_flag: &'a Arc<AtomicBool>,
chunk_id: ChunkId,
) -> Self {
Self {
samples,
params,
abort_flag,
chunk_id,
}
}
#[must_use]
pub const fn samples(&self) -> &'a [f32] {
self.samples
}
#[must_use]
pub const fn params(&self) -> &'a AsrParams {
self.params
}
#[must_use]
pub const fn abort_flag(&self) -> &'a Arc<AtomicBool> {
self.abort_flag
}
#[must_use]
pub const fn chunk_id(&self) -> ChunkId {
self.chunk_id
}
}
#[cfg(feature = "runner")]
pub struct WhisperAsrSource {
ctx: Arc<WhisperContext>,
state: WhisperState,
}
#[cfg(feature = "runner")]
impl WhisperAsrSource {
pub fn new(ctx: Arc<WhisperContext>) -> Result<Self, crate::runner::RunnerError> {
let state = ctx
.create_state()
.map_err(|e| crate::runner::RunnerError::WhisperContextLoad {
message: format_smolstr!("create_state failed: {e:?}"),
})?;
Ok(Self { ctx, state })
}
pub fn context(&self) -> &Arc<WhisperContext> {
&self.ctx
}
}
#[cfg(feature = "runner")]
impl AsrSource for WhisperAsrSource {
fn run_chunk(&mut self, chunk: AsrChunkContext<'_>) -> Result<AsrResult, WorkFailure> {
use crate::runner::whisper_pool::{
AsrWorkItem, run_with_temperature_ladder, validate_for_whisper_ffi,
};
validate_for_whisper_ffi(chunk.params())?;
if let Some((idx, val)) = chunk
.samples()
.iter()
.copied()
.enumerate()
.find(|(_, s)| !s.is_finite())
{
return Err(WorkFailure::Asr(AsrError::Backend(AsrFailure::new(
format_smolstr!(
"non-finite ASR sample at index {idx} (value {val:?}); upstream audio corruption — \
refuse to encode rather than poison whisper.cpp's float math"
),
))));
}
let job = AsrWorkItem {
chunk_id: chunk.chunk_id(),
samples: Arc::<[f32]>::from(chunk.samples().to_vec()),
params: chunk.params().clone(),
abort_flag: chunk.abort_flag().clone(),
};
let started_at = std::time::Instant::now();
run_with_temperature_ladder(&mut self.state, &job, started_at)
}
}
#[cfg(test)]
#[cfg(feature = "runner")]
mod tests {
use super::*;
fn assert_send<T: Send>() {}
#[test]
fn whisper_asr_source_is_send() {
assert_send::<WhisperAsrSource>();
}
#[test]
fn asr_source_is_object_safe() {
fn _take(_: &mut dyn AsrSource) {}
}
}