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

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. Set a sane thread count (>= 1, typically min(4, NumCPU)) and confirm the prompt string is NUL-terminated before crossing cgo.
  6. 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

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


AI-assisted analysis of mudler/LocalAI@44413a9d06 (2026-08-15). Data as JSON: /api/errors/1b5d6b384fd1797a. Report an issue: GitHub.