fix(chatterbox-fast): correct-length WAV header for one-shot responses
stream=false + format=wav emitted the streaming 0xFFFFFFFF-length header, so a buffered consumer reading a complete wav got bogus RIFF/data sizes. One-shot knows the full length, so emit correct sizes; streaming keeps the open-ended header (length genuinely unknown up front). Verified remote: one-shot wav data size == bytes-44, python wave.open() reads 2.20s cleanly; streaming still 0xFFFFFFFF.
This commit is contained in:
@@ -217,12 +217,15 @@ def _pcm16(wav: torch.Tensor) -> bytes:
|
||||
return (a * 32767.0).astype("<i2").tobytes()
|
||||
|
||||
|
||||
def _wav_header(sr: int) -> bytes:
|
||||
"""Streaming WAV header with unknown length (0xFFFFFFFF sizes)."""
|
||||
def _wav_header(sr: int, data_len: int | None = None) -> bytes:
|
||||
"""WAV header. data_len=None → streaming (length unknown, 0xFFFFFFFF sizes,
|
||||
player reads to EOF); an int → correct RIFF/data sizes for a complete file."""
|
||||
data_size = 0xFFFFFFFF if data_len is None else data_len
|
||||
riff_size = 0xFFFFFFFF if data_len is None else 36 + data_len
|
||||
return (
|
||||
b"RIFF" + struct.pack("<I", 0xFFFFFFFF) + b"WAVE"
|
||||
b"RIFF" + struct.pack("<I", riff_size) + b"WAVE"
|
||||
+ b"fmt " + struct.pack("<IHHIIHH", 16, 1, 1, sr, sr * 2, 2, 16)
|
||||
+ b"data" + struct.pack("<I", 0xFFFFFFFF)
|
||||
+ b"data" + struct.pack("<I", data_size)
|
||||
)
|
||||
|
||||
|
||||
@@ -276,19 +279,25 @@ def tts(req: TTSRequest) -> StreamingResponse:
|
||||
# workload); concurrent callers queue rather than corrupt conditionals.
|
||||
with engine.lock:
|
||||
engine._prepare(voice_path, exaggeration=req.exaggeration)
|
||||
if req.format == "wav":
|
||||
yield _wav_header(engine.sr)
|
||||
|
||||
t_req = time.perf_counter()
|
||||
first_audio_ms: float | None = None
|
||||
|
||||
if not req.stream:
|
||||
# One-shot: the full length is known, so emit a correct-sized WAV
|
||||
# header (a buffered consumer wants well-formed sizes).
|
||||
wav, audio_sec = engine.generate(req.text, req)
|
||||
first_audio_ms = (time.perf_counter() - t_req) * 1000
|
||||
log.info("oneshot: %.0fms gen, %.2fs audio", first_audio_ms, audio_sec)
|
||||
yield _pcm16(wav)
|
||||
pcm = _pcm16(wav)
|
||||
if req.format == "wav":
|
||||
yield _wav_header(engine.sr, len(pcm))
|
||||
yield pcm
|
||||
return
|
||||
|
||||
# Streaming: length is unknown up front → open-ended WAV header.
|
||||
if req.format == "wav":
|
||||
yield _wav_header(engine.sr)
|
||||
|
||||
def _gen(text: str) -> tuple[torch.Tensor, float]:
|
||||
return engine.generate(text, req)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user