You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
parakeet-cli: support stdin / pipe input (--input -) #38
parakeet-cli transcribe --input only accepts a file path. There is no way to pipe audio from stdin (e.g. from ffmpeg or curl), forcing a temp-file workaround.
audio_io.cpp uses dr_wav_open_file_and_read_pcm_frames_f32(path) which opens the file via fopen. dr_wav seeks within the file to parse the WAV header and locate the data chunk. On a pipe (named or anonymous), seeking fails or degrades to a slow read-all path.
Suggested fix
Support --input - to read WAV from stdin. The implementation would:
Read stdin into a std::vector<uint8_t> buffer
Call dr_wav_open_memory_and_read_pcm_frames_f32() on the buffer
This avoids the seek problem entirely and is the standard convention (- = stdin) used by ffmpeg, whisper.cpp, and most CLI tools.
Investigated the vendored third_party/dr_wav.h (v0.14.6). The degradation is not from the WAV header size (44 bytes) — it's from an unconditional absolute seek that dr_wav performs even when the file pointer is already at the right position.
The seek that kills pipes
In drwav_init__internal (the init path used by drwav_open_file_and_read_pcm_frames_f32), after the chunk-parsing loop finds the "data" chunk and records dataChunkDataPos, the code unconditionally seeks back to it:
// dr_wav.h line ~3707 — UNCONDITIONAL, runs for every fileif (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) {
returnDRWAV_FALSE;
}
For a standard 44-byte PCM WAV, dataChunkDataPos is 44 — exactly where the read cursor already sits after parsing the header. The seek is a semantic no-op, but fseek(fp, 44, SEEK_SET) calls lseek(SEEK_SET) which fails with ESPIPE on a FIFO. Pipes reject any seek, even to the current position.
Why it's slow rather than failing instantly
drwav__seek_from_start (line ~2014) and drwav__seek_forward (line ~1994) have read-and-discard fallbacks when onSeek returns false — they read and discard bytes to reach the target position instead of seeking. On a pipe this means:
Every "skip chunk" seek → read-and-discard the chunk's bytes
The absolute seek to dataChunkDataPos → read-and-discard back to position 44 (which on a pipe means re-reading from the start, but the data is gone — so it blocks waiting for more data that never comes, or degrades to pathological small reads)
dr_wav also does many seeks during init (skip fmt padding at line 3402/3409, rewind for metadata at 3324/3722, seek to data at 3707/3798). Each one degrades to a read-and-discard loop on a pipe. That's the 17.6× slowdown.
The DRWAV_SEQUENTIAL flag doesn't help
drwav_init_file passes flags=0, so DRWAV_SEQUENTIAL is never set. But even with DRWAV_SEQUENTIAL, the absolute seek at line 3707 still runs unconditionally — it's not guarded by the sequential flag.
Proposed fix (two options)
Option A — minimal: guard the seek with a tell check (in dr_wav.h, drwav_init__internal after the loop):
// Only seek if we're not already at dataChunkDataPosdrwav_int64currentPos;
if (pWav->onTell(pWav->pUserData, ¤tPos) &&
(drwav_uint64)currentPos==pWav->dataChunkDataPos) {
// already there — skip the seek (works on pipes)
} elseif (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) {
returnDRWAV_FALSE;
}
Option B — upstream-idiomatic: use drwav_init_memory after buffering stdin (in src/audio_io.cpp):
boolload_audio_16k_mono(const std::string& path, Audio& out) {
if (path == "-") {
// Read stdin into buffer, then use memory-based dr_wav
std::vector<uint8_t> buf;
char tmp[65536];
while (true) {
size_t n = fread(tmp, 1, sizeof(tmp), stdin);
buf.insert(buf.end(), tmp, tmp + n);
if (n < sizeof(tmp)) break;
}
unsignedint ch = 0, sr = 0; drwav_uint64 frames = 0;
float* pcm = drwav_open_memory_and_read_pcm_frames_f32(
buf.data(), buf.size(), &ch, &sr, &frames, nullptr);
// ... rest same as below
}
// existing file path code...
}
Option A fixes it for all pipes (no buffering needed, no memory waste for large files). Option B is the --input - convention from the original issue. Both should be implemented — A in dr_wav upstream, B in parakeet.cpp's CLI.
References in code
src/audio_io.cpp:24 — the call site: drwav_open_file_and_read_pcm_frames_f32(path.c_str(), ...)
third_party/dr_wav.h:3707 — the unconditional seek
Activity
HaleTom commentedon Jun 26, 2026
Root cause found in
dr_wav.hInvestigated the vendored
third_party/dr_wav.h(v0.14.6). The degradation is not from the WAV header size (44 bytes) — it's from an unconditional absolute seek that dr_wav performs even when the file pointer is already at the right position.The seek that kills pipes
In
drwav_init__internal(the init path used bydrwav_open_file_and_read_pcm_frames_f32), after the chunk-parsing loop finds the"data"chunk and recordsdataChunkDataPos, the code unconditionally seeks back to it:For a standard 44-byte PCM WAV,
dataChunkDataPosis 44 — exactly where the read cursor already sits after parsing the header. The seek is a semantic no-op, butfseek(fp, 44, SEEK_SET)callslseek(SEEK_SET)which fails withESPIPEon a FIFO. Pipes reject any seek, even to the current position.Why it's slow rather than failing instantly
drwav__seek_from_start(line ~2014) anddrwav__seek_forward(line ~1994) have read-and-discard fallbacks whenonSeekreturns false — they read and discard bytes to reach the target position instead of seeking. On a pipe this means:dataChunkDataPos→ read-and-discard back to position 44 (which on a pipe means re-reading from the start, but the data is gone — so it blocks waiting for more data that never comes, or degrades to pathological small reads)dr_wav also does many seeks during init (skip fmt padding at line 3402/3409, rewind for metadata at 3324/3722, seek to data at 3707/3798). Each one degrades to a read-and-discard loop on a pipe. That's the 17.6× slowdown.
The
DRWAV_SEQUENTIALflag doesn't helpdrwav_init_filepassesflags=0, soDRWAV_SEQUENTIALis never set. But even withDRWAV_SEQUENTIAL, the absolute seek at line 3707 still runs unconditionally — it's not guarded by the sequential flag.Proposed fix (two options)
Option A — minimal: guard the seek with a tell check (in
dr_wav.h,drwav_init__internalafter the loop):Option B — upstream-idiomatic: use
drwav_init_memoryafter buffering stdin (insrc/audio_io.cpp):Option A fixes it for all pipes (no buffering needed, no memory waste for large files). Option B is the
--input -convention from the original issue. Both should be implemented — A in dr_wav upstream, B in parakeet.cpp's CLI.References in code
src/audio_io.cpp:24— the call site:drwav_open_file_and_read_pcm_frames_f32(path.c_str(), ...)third_party/dr_wav.h:3707— the unconditional seekthird_party/dr_wav.h:2014—drwav__seek_from_start(absolute seek helper)third_party/dr_wav.h:1994—drwav__seek_forward(relative seek with read-discard fallback)