mudler/LocalAI · error · Error
Web Audio API not available
Error message
Web Audio API not available
What it means
This error is printed by the cgo bridge when whisper_full() — whisper.cpp's main transcription entry point — returns a nonzero status. whisper_full returns nonzero when the encoder/decoder compute fails, when language auto-detection fails (typically on silence or extremely short audio), or when the input cannot be processed at all (null/empty PCM, model/context mismatch). The code first distinguishes the intentional client abort path (g_abort set from Go via set_abort(1), which returns 2), so return code 1 here means a genuine processing failure, not cancellation.
Source
Thrown at core/http/react-ui/src/hooks/useSpectrogram.js:48
// hundred px wide, so computing an FFT per native hop would be wasted work.
export default function useSpectrogram(
src,
{ fftSize = 512, hop = 256, maxFrames = 900, dbFloor = -90 } = {},
) {
const [state, setState] = useState(EMPTY)
useEffect(() => {
setState(EMPTY)
if (!src) return
let cancelled = false
setState((s) => ({ ...s, loading: true }))
async function run() {
try {
const resp = await fetch(src)
const raw = await resp.arrayBuffer()
const ctx = getSharedAudioContext()
if (!ctx) throw new Error('Web Audio API not available')
const audio = await ctx.decodeAudioData(raw.slice(0))
if (cancelled) return
const data = audio.getChannelData(0)
const bins = fftSize >> 1
const win = hann(fftSize)
// Frame count, then a stride so we never run more than maxFrames FFTs.
const rawFrames = data.length >= fftSize ? 1 + Math.floor((data.length - fftSize) / hop) : 1
const stride = rawFrames > maxFrames ? Math.ceil(rawFrames / maxFrames) : 1
const frames = Math.ceil(rawFrames / stride)
const spec = new Float32Array(frames * bins)
const re = new Float64Array(fftSize)
const im = new Float64Array(fftSize)
let peakDb = dbFloor
for (let f = 0; f < frames; f++) {View on GitHub (pinned to 44413a9d06)
Solutions
- Check the ggml [ERROR]/[WARN] log lines emitted just above this message — they state the exact internal failure (e.g. 'failed to auto-detect language', 'failed to encode', 'ggml_backend_alloc failed'); fix that root cause first.
- Guard the caller: skip transcribe() when pcmf32_len is 0 or when VAD reported no speech segments; whisper_full on empty input is guaranteed to fail.
- If lang is empty, auto-detect runs on silence-prone audio — pass an explicit language (e.g. 'en') or ensure audio has detectable speech; upgrade/verify the whisper model GGUF is intact (re-download if the hash or file size looks wrong).
- Serialize access to transcribe() — the shim uses a single static whisper_context, so wrap calls in a mutex on the Go side to prevent interleaved whisper_full calls.
- Set a sane thread count (>= 1, typically min(4, NumCPU)) and confirm the prompt string is NUL-terminated before crossing cgo.
- If the failure appeared after a whisper.cpp/ggml update, rebuild the gowhisper shim from source so headers and library match.
Example fix
// Go caller: validate input and serialize before crossing into C
// before
text, err := gw.Transcribe(threads, lang, translate, tdrz, pcm)
// after
mu.Lock() // single static whisper_context in the shim: no concurrent whisper_full
defer mu.Unlock()
if len(pcm) == 0 {
return "", errors.New("no audio to transcribe")
}
if len(segs) == 0 { // VAD found no speech
return "", nil
}
text, err := gw.Transcribe(threads, "en", translate, tdrz, pcm)
if err == gw.ErrAborted { // return code 2 from C
return "", context.Canceled
} Defensive patterns
Strategy: validation
Validate before calling
// Run before transcribe(): non-empty audio, explicit language, loaded model
func canTranscribe(sampleCount int, modelLoaded bool, lang string) error {
if !modelLoaded { // load_model() must have returned 0
return errors.New("whisper model not loaded")
}
if sampleCount < 320 { // whisper needs >= 30 ms; use >= 1 s in practice
return fmt.Errorf("audio too short to transcribe: %d samples", sampleCount)
}
if lang == "" && sampleCount < 16000 { // auto-detect needs enough speech
return errors.New("explicit language required for audio < 1 s")
}
return nil
} Type guard
// Go: distinguish the two nonzero return codes from transcribe()
var errAborted = errors.New("transcription aborted by client")
func isAbortRC(rc int) bool { return rc == 2 }
func isHardFailRC(rc int) bool { return rc == 1 } Try / catch
// Map C return codes at the cgo boundary; never retry rc==1 blindly
rc := C.transcribe(C.uint32_t(threads), cLang, b2i(translate), b2i(tdrz),
(*C.float)(&pcm[0]), C.size_t(len(pcm)), &segLen, cPrompt)
switch rc {
case 0: // success
case 2: // client abort via set_abort(1): not an error, propagate cancellation
return context.Canceled
case 1: // hard failure: inspect ggml log output, do not retry same input
return fmt.Errorf("transcription failed (rc=1): check ggml log; len=%d lang=%q", len(pcm), lang)
} Prevention
- Never call transcribe() with an empty PCM slice; short-circuit when VAD yields zero segments
- Serialize transcribe() calls behind a mutex — the shim holds one static whisper_context
- Pass an explicit language instead of relying on auto-detect for short or noisy clips
- Cap and validate thread count (1..NumCPU) and NUL-terminate lang/prompt strings before crossing cgo
- Rebuild the gowhisper shim whenever whisper.cpp or ggml is updated to keep the ABI aligned
- Capture the ggml log lines on failure — they name the real internal error that caused whisper_full to return nonzero
When it happens
Trigger: Calling transcribe() with pcmf32_len == 0 or a near-empty buffer after VAD filtering removed everything; passing lang="" causes wparams.language = nullptr which forces whisper auto-detection, which fails (returns -3 in whisper.cpp) on pure silence/noise; a ggml backend failure during whisper_full (failed to encode, CUDA/Metal OOM, missing backend in the prebuilt library); thread count of 0 or a value the runtime rejects; a prompt buffer that is not NUL-terminated (it is printed with %s and passed as initial_prompt); version mismatch between the shim and the vendored whisper.cpp causing ABI-level misreads of whisper_full_params.
Common situations: LocalAI users sending silent or sub-second audio files; enabling tdrz (tinydiarize) with a non-tdrz model; GPU memory exhaustion when a larger whisper model is loaded alongside other backends; a stale gowhisper.a built against older whisper.cpp headers after a dependency bump; concurrent calls reusing the single static ctx (this shim has one global whisper_context, so overlapping transcribe() calls corrupt state and can fail nondeterministically).
Related errors
- Web Audio API not available
- Normalized audio is larger than 50 MiB
- voiceCreate.audio.durationError
- model snapshot does not exist: {model_ref}
- model snapshot must contain exactly one {suffix} file; found
AI-assisted analysis of mudler/LocalAI@44413a9d06 (2026-08-15).
Data as JSON: /api/errors/1b5d6b384fd1797a.
Report an issue: GitHub.