1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Stereo / `channels=split` transcription.
use super::*;
impl Engine {
/// Transcribe a multi-channel recording with one speaker label per channel.
///
/// Runs the engine once per channel sequentially on the supplied triplet. The
/// caller is responsible for deciding whether to use this mode (e.g. after
/// checking for dual-mono). Channel 0 becomes `speaker_0`, channel 1
/// `speaker_1`, and so on. Results are merged into a single chronologically
/// ordered transcript.
///
/// Per-channel `text` fields are ignored: the merged transcript's text is
/// rebuilt from the merged words after the final ITN/punctuation pass.
///
/// Thin wrapper over [`Engine::transcribe_request`] with default overrides
/// and no hotwords.
#[cfg(feature = "file-decode")]
pub fn transcribe_channels(
&self,
channels: &[Vec<f32>],
triplet: &mut SessionTriplet,
) -> Result<TranscribeResult, GigasttError> {
self.transcribe_request(
TranscribeRequest::new(TranscribeSource::Channels(channels)),
triplet,
)
}
/// Per-channel decode + merge used by [`TranscribeSource::Channels`].
pub(crate) fn transcribe_channels_inner(
&self,
channels: &[Vec<f32>],
triplet: &mut SessionTriplet,
overrides: &TranscribeOverrides,
hotwords: Option<&HotwordOverride>,
ctl: DecodeControls,
) -> Result<TranscribeResult, GigasttError> {
if channels.is_empty() {
return Ok(TranscribeResult {
text: String::new(),
words: Vec::new(),
duration_s: 0.0,
confidence: None,
});
}
let mut per_channel = Vec::with_capacity(channels.len());
for channel_samples in channels {
let words =
self.decode_words_for_samples(channel_samples, triplet, overrides, hotwords, ctl)?;
let duration_s = channel_samples.len() as f64 / 16000.0;
per_channel.push(TranscribeResult {
confidence: aggregate_confidence(&words),
text: String::new(),
words,
duration_s,
});
}
let merged = merge_channel_results(per_channel);
Ok(self.finish_transcribe_result(merged.words, merged.duration_s, overrides))
}
/// Streaming twin of the `channels=split` decode.
///
/// Each channel of `data` runs through the windowed loop the mono file path
/// uses, so
/// peak audio memory is one window instead of every channel of the whole
/// file — which is what kept channel splitting on a duration ceiling. The
/// per-channel results are merged exactly as the whole-buffer twin merges
/// them.
///
/// The caller decides *whether* to split; use
/// [`scan_channels`](crate::inference::audio::scan_channels), which answers
/// that in one pass without materializing anything.
///
/// No progress sink is threaded through: each channel restarts the sample
/// clock, so a shared monotonic counter would go backwards — the same
/// reason the whole-buffer twin passes `abort_only`.
// Bundling these behind `&TranscribeRequest` is what one would want, but the
// caller's match moves `data` out of `req.source`, so the request cannot be
// borrowed whole afterwards. Same shape as `run_inference` above.
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "file-decode")]
pub(crate) fn transcribe_channel_streams(
&self,
data: bytes::Bytes,
channels: usize,
max_audio_secs: Option<f64>,
triplet: &mut SessionTriplet,
overrides: &TranscribeOverrides,
hotwords: Option<&HotwordOverride>,
ctl: DecodeControls,
) -> Result<TranscribeResult, GigasttError> {
let request_biaser = hotwords.and_then(|hw| self.build_request_biaser(hw));
let biaser = self.select_biaser(hotwords, &request_biaser);
let spec = window_spec(self.ane_encoder, self.variant.is_ctc());
let mut per_channel = Vec::with_capacity(channels);
for k in 0..channels {
let mut windows =
audio::FileWindows::from_bytes_channel(data.clone(), spec, max_audio_secs, k)
.map_err(audio::decode_error)?;
let words = self.decode_words_streaming(&mut windows, triplet, biaser, ctl)?;
per_channel.push(TranscribeResult {
confidence: aggregate_confidence(&words),
text: String::new(),
words,
duration_s: windows.total_16k_samples() as f64 / 16000.0,
});
}
let merged = merge_channel_results(per_channel);
Ok(self.finish_transcribe_result(merged.words, merged.duration_s, overrides))
}
}