Voice input (Linux/PulseAudio): audio streamed to STT is ~6× undersampled vs declared 16 kHz → always "no speech detected"

Open 💬 0 comments Opened Jun 9, 2026 by dzaugg64

Summary

On Linux with PulseAudio, voice input (/voice) always returns "no speech detected", even though the microphone is correctly configured and captures clean audio at the OS level. Debug logs show Claude Code opens the capture device at 48000 Hz float32, connects to the speech-to-text websocket declaring linear16 / 16000 Hz / mono, and then streams audio at roughly 6× too low a byte rate for that declared format. The server receives only ~1/6 of the spoken duration and returns an empty transcript.

Environment

  • Claude Code 2.1.170 (also reproduced on 2.1.169), native installer
  • Linux, PulseAudio 17.0, xfce4-terminal (VTE)
  • Account: Claude.ai / Max (not an API key)
  • Microphone: USB "Realtek USB2.0 Audio" class device, native rate 16000 Hz, set as the default PulseAudio source

What works (ruled out)

  • The mic is the default PulseAudio source. Capture verified independently through the exact same PulseAudio path: arecord -D pulse -f S16_LE -r 16000 -c 1 yields a strong, clean signal (peak ~47%).
  • Routing, capture level, mute state and device selection are all correct. The live level meter in the voice UI moves in sync with my voice.
  • Auth is Claude.ai/Max (voice is supported on this account type).
  • Updating 2.1.169 → 2.1.170 does not change the behavior.

Debug evidence (--debug-file)

[voice_stream] Connecting to wss://api.anthropic.com/api/ws/speech_to_text/voice_stream?encoding=linear16&sample_rate=16000&channels=1&endpointing_ms=300&utterance_end_ms=1000&language=fr&use_conversation_engine=true&stt_provider=deepgram-nova3
[voice] onReady: flushing 3 buffered chunks (2544 bytes) as 1 coalesced frame(s)
[voice_stream] Sending audio chunk: 800 bytes        <-- repeated ~every 160 ms for ~7 s
...
[voice] finishRecording: stopping recording, transitioning to processing
[voice_stream] Sending CloseStream (finalize)
[voice_stream] WebSocket closed: code=1000 reason=""
[voice] Final transcript assembled (0 chars)

The math (the bug)

  • Declared format: linear16, 16000 Hz, mono = 32000 bytes/s.
  • Actually streamed: 800 bytes every ~160 ms ≈ 5000 bytes/s.
  • Ratio ≈ 6.4× too little, which factors cleanly as 3× (48000 vs 16000 Hz) × 2× (float32 4-byte vs int16 2-byte).
  • For ~7.4 s spoken, only ~37.7 kB is streamed ≈ 1.18 s of "16 kHz" audio → empty transcript.

Root-cause hypothesis

PulseAudio reports Claude Code's capture source-output as float32le / 48000 Hz / 1ch (application name ALSA plug-in [2.1.170]), while the device is natively 16 kHz. The conversion from 48 kHz float32 down to the declared 16 kHz linear16 appears to mis-account for the sample rate and/or the sample width, under-delivering audio to the encoder.

Expected behavior: resample the captured audio to exactly the declared 16 kHz linear16 before streaming (or declare the true captured rate to the STT endpoint).

Reproduction

  1. Linux + PulseAudio, with a USB mic (natively 16 kHz) set as the default source.
  2. /voice (hold mode), speak for several seconds.
  3. Always "no speech detected"; the debug log shows the byte-rate undersend above.

View original on GitHub ↗