Description
Currently, the POST /v1/audio/transcriptions endpoint strictly expects raw WAV PCM audio uploads (RIFF....WAVE header).
When integrating audio.cpp with popular web clients like Open WebUI, transcription fails because modern web browsers (Chrome, Edge, Firefox, Safari) using the HTML5 MediaRecorder API record microphone input in audio/webm;codecs=opus (or audio/ogg). Browsers do not support recording native WAV via MediaRecorder.
As a result, sending microphone recordings from Open WebUI to any ASR model (e.g. parakeet_tdt, granite5asr, whisper) fails with:
Error transcribing chunk: External: 500, message='Internal Server Error', url='http://:8080/v1/audio/transcriptions'
In contrast, clients that buffer and convert audio to WAV beforehand (such as Home Assistant via wyoming_openai or curl -F file=@sample.wav) succeed with exceptional sub-30ms latency.
Steps to Reproduce
- Start
audiocpp_server with any ASR model (e.g., parakeet_tdt or granite5asr):
{
"models": [
{
"id": "parakeet_tdt",
"family": "parakeet_tdt",
"path": "/models/parakeet-tdt-0.6b-v3-q8_0.gguf",
"task": "asr",
"mode": "offline"
}
]
}
- In Open WebUI (Admin Settings ➔ Audio):
- Set Speech-to-Text Engine: OpenAI
- Set API Base URL: http://:8080/v1
- Set STT Model: parakeet_tdt
- Click the microphone button in Open WebUI chat to record speech.
- Open WebUI fails with HTTP 500.
Root Cause
handle_transcription_multipart in app/server/runtime.cpp checks is_wav_upload_filename and directly parses bytes using engine::audio::read_wav_f32(), rejecting .webm, .mp3, .ogg, and .m4a files.
When browsers send MediaRecorder chunks or blobs (often named audio.webm or blob), the server throws an unhandled exception or returns a 400/500 error instead of decoding the compressed audio stream.
Suggested Fixes & Solutions
Solution 1: Lightweight In-Tree Decoders (Recommended)
Embed header-only C/C++ audio decoders (similar to how ggml or llama.cpp bundle dependencies) so audio.cpp remains self-contained without external runtime dependencies:
miniaudio (Public Domain / MIT, single-file) — handles MP3, FLAC, WAV, and Vorbis decoding natively.
opusfile / libopus — decodes WebM/Ogg Opus streams directly to 16 kHz float32 PCM in memory.
Solution 2: Optional Server-Side Transcoder Bridge
If external tools are acceptable when present, allow audiocpp_server to detect and pipe incoming non-WAV multipart bytes through ffmpeg or libavcodec to convert to 16 kHz mono WAV PCM in memory before running inference.
Solution 3: Graceful Handling of Empty / Incomplete Chunks
When MediaRecorder begins recording, the initial chunk may sometimes be 0 bytes or header-incomplete. Returning {"text": ""} (HTTP 200) instead of throwing an unhandled exception ensures the client does not crash before the full audio is delivered.
Description
Currently, the
POST /v1/audio/transcriptionsendpoint strictly expects raw WAV PCM audio uploads (RIFF....WAVEheader).When integrating
audio.cppwith popular web clients like Open WebUI, transcription fails because modern web browsers (Chrome, Edge, Firefox, Safari) using the HTML5MediaRecorderAPI record microphone input inaudio/webm;codecs=opus(oraudio/ogg). Browsers do not support recording native WAV viaMediaRecorder.As a result, sending microphone recordings from Open WebUI to any ASR model (e.g.
parakeet_tdt,granite5asr,whisper) fails with:Error transcribing chunk: External: 500, message='Internal Server Error', url='http://:8080/v1/audio/transcriptions'In contrast, clients that buffer and convert audio to WAV beforehand (such as Home Assistant via
wyoming_openaiorcurl -F file=@sample.wav) succeed with exceptional sub-30ms latency.Steps to Reproduce
audiocpp_serverwith any ASR model (e.g.,parakeet_tdtorgranite5asr):{ "models": [ { "id": "parakeet_tdt", "family": "parakeet_tdt", "path": "/models/parakeet-tdt-0.6b-v3-q8_0.gguf", "task": "asr", "mode": "offline" } ] }Root Cause
handle_transcription_multipart in app/server/runtime.cpp checks is_wav_upload_filename and directly parses bytes using engine::audio::read_wav_f32(), rejecting .webm, .mp3, .ogg, and .m4a files.
When browsers send MediaRecorder chunks or blobs (often named audio.webm or blob), the server throws an unhandled exception or returns a 400/500 error instead of decoding the compressed audio stream.
Suggested Fixes & Solutions
Solution 1: Lightweight In-Tree Decoders (Recommended)
Embed header-only C/C++ audio decoders (similar to how ggml or llama.cpp bundle dependencies) so audio.cpp remains self-contained without external runtime dependencies:
miniaudio (Public Domain / MIT, single-file) — handles MP3, FLAC, WAV, and Vorbis decoding natively.
opusfile / libopus — decodes WebM/Ogg Opus streams directly to 16 kHz float32 PCM in memory.
Solution 2: Optional Server-Side Transcoder Bridge
If external tools are acceptable when present, allow audiocpp_server to detect and pipe incoming non-WAV multipart bytes through ffmpeg or libavcodec to convert to 16 kHz mono WAV PCM in memory before running inference.
Solution 3: Graceful Handling of Empty / Incomplete Chunks
When MediaRecorder begins recording, the initial chunk may sometimes be 0 bytes or header-incomplete. Returning {"text": ""} (HTTP 200) instead of throwing an unhandled exception ensures the client does not crash before the full audio is delivered.