The prompt is fed at positions 0..n-1, but generated tokens were fed at
1, 2, ... instead of n, n+1, ..., overlapping the prompt. Feed them at
decoder_input.size() + i - 1, as NeMo's GreedySequenceGenerator does.
Fixes#4096
Fixes#3635
It is for v2.0.0 and breaks the ABI.
CircularBuffer::head_ and tail_ are absolute sample counters that grow
forever. As int32_t they overflow after 2^31 samples (~37 h at 16 kHz);
tail_ % capacity then goes negative and std::copy() writes to an invalid
iterator, crashing long-running VAD streams.
Widen every absolute stream position to int64_t. Counts and capacities
(Push/Pop/Get's n, Size(), capacity) stay int32_t — they are bounded by
buffer memory, not stream duration.
NeMo's AudioToMelSpectrogramPrefab computes log(x + 2**-24), while the
shared fbank extractor applies a hard floor, log(max(x, FLT_EPSILON)).
On quiet recordings a large share of the mel bins sit at that floor and
per-feature normalization turns them into constant columns the model
never saw in training, which degrades accuracy a lot (issue #3997).
For NeMo models (nemo_normalize_type is set), disable use_log_fbank and
apply log(x + 2**-24) to the raw mel energies in GetFrames() before the
per-feature normalization. Other models keep the existing behavior.
Bump the dependency and select the additive guard with eps = FLT_EPSILON / 2 for NeMo fbank models. The manual log pass in GetFrames() is no longer needed, which also keeps MFCC models such as TeleSpeech untouched.
The wav2vec2-style convolutional front end of omnilingual ASR models needs at
least 400 samples. For shorter input onnxruntime throws an Ort::Exception from
DecodeStream that nothing catches, so sherpa-onnx-offline and C API users abort,
Python users get a RuntimeError, and a batch passed to DecodeStreams loses the
results of every stream. Empty input also reached f.size() / feat_dim with
feat_dim == 0. Return an empty result for such input instead.
Both examples passed each 0.1 s read straight to accept_waveform(), which expects
window-sized input. Buffer the reads and feed window_size samples per call, as the
other VAD examples do.
A lead byte announcing a 2-4 byte character near the end of the input was
copied as a whole character even when fewer bytes remained, reading past
the end of the string. This happens for TTS text that ends in the middle
of a character, which reaches SplitUtf8 through the lexicon front ends.
Treat such a lead byte like any other invalid byte and skip it.
The timestamps were built as str(timedelta(seconds=t))[:-3]. That only
works when t has a fractional part: for a whole number of seconds
str(timedelta) has no ".ffffff" suffix, so the slice cuts off the
seconds instead, e.g. a segment starting at 0 s was written as
"0:00 --> 0:00:01,500". Hours were also written with a single digit,
while SRT expects HH:MM:SS,mmm.
Format the timestamps explicitly as HH:MM:SS,mmm for both the .srt
and the .txt output.
- profile_output_dirs(): under a custom Cargo profile (e.g. dx's android-dev), PROFILE is
debug/release but the directory is named after the profile. Fall back to Cargo's fixed
layout, OUT_DIR.ancestors().nth(3).
- download_prebuilt_libs(): only consult the cached jniLibs/<abi> directory for Android; iOS
aarch64 maps to the same arm64-v8a name and picked up Android libraries.
- iOS: reuse an already extracted xcframework instead of re-extracting, which deleted the
other target's lib dir (device lib/ vs simulator lib-sim/).
ToFloat() de-interleaves the captured frames, so samples1_ holds one
float per frame (the first channel), while samples_ still holds
actual_channel_count_ int16 samples per frame. Passing samples_.size()
as input_dim to LinearResample::Resample() made it read twice as many
floats as samples1_ contains whenever the device runs with 2 channels
and a sample rate that needs resampling, reading past the end of the
buffer and producing a too long output with garbage tail.
With keywords that cannot be encoded, e.g. a token missing from tokens.txt, the C++
CreateStream logs the error and returns nullptr, which reached Python as None. The
caller then failed later with "'NoneType' object has no attribute 'accept_waveform'".
Raise a ValueError naming the keywords instead.
Fixes#3731
espeak-ng and piper-phonemize are no longer built into sherpa-onnx.
Text frontends are now:
- **lexicon** — built in, no extra dependency
- **phonemize** — phonemize outside sherpa-onnx (e.g. [piper-phonemize](https://github.com/csukuangfj/piper-phonemize)) and pass the result in. That library is GPL, so it stays optional: sherpa-onnx is GPL-free after this PR.
## API
- `SherpaOnnxGenerationConfig` gains `phoneme_codepoints` and `tokens` (mutually exclusive alternatives to `text`)
- `data_dir` on the TTS model configs is deprecated and ignored; the ABI slot is kept
- all bindings updated: C/C++, Python, Java, Kotlin, Go, Rust, C#, Swift, Pascal, Dart/Flutter, node-addon, WASM, HarmonyOS
The three RUSTSEC advisories against zip 2.x (RUSTSEC-2026-0367 SUID/SGID bits, RUSTSEC-2026-0368 symlink escape, RUSTSEC-2026-0369 permissions through symlinks) are fixed in zip >= 9 only. The zip usage in build.rs is confined to the iOS .xcframework.zip branch and its ZipArchive::new/extract API is unchanged in 9.x, so the bump is source-compatible: verified with `DOCS_RS=1 cargo check` against zip 9.0.4.
Fixes#4058
The C struct SherpaOnnxOfflineRecognizerConfig gained
ctc_fst_decoder_config, but the Swift helper constructing it was not
updated, so the swift CI workflow fails to compile (missing argument
for parameter 'ctc_fst_decoder_config'). Mirror the online wrapper:
add sherpaOnnxOfflineCtcFstDecoderConfig() and pass the new field
(with a default value so existing callers are unaffected).
An exception escaping a JNI boundary calls std::terminate and aborts the
process. In production we observed an Ort::Exception raised while loading
a corrupt model file inside OfflineRecognizer::newFromFile take down the
whole app, uncatchable from Kotlin.
Wrap every model-loading JNI constructor (offline/online recognizer,
offline/online punctuation, both the file and the asset variants) in a
try/catch that converts the C++ exception into a Java exception via
ThrowNew, following the existing pattern in jni/wave-reader.cc. The
helper RethrowCxxExceptionAsJava lives in jni/common.h.
Fixes#3987
* Add subtitle generation Python API example for Doubao ASR.
generate-subtitles-doubao.py uses silero-vad/ten-vad for segmentation and
the Doubao bigmodel recording-file recognition API for ASR. Audio segments
are sent as base64 encoded wav data, so no public URL is needed. Results
are written to <input>.srt and <input>.txt.
Also for generate-subtitles.py:
- Add --max-duration-seconds to process only the first N seconds of the
input file
- Refuse to run if <input>.srt or <input>.txt already exists