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()
|
return (a * 32767.0).astype("<i2").tobytes()
|
||||||
|
|
||||||
|
|
||||||
def _wav_header(sr: int) -> bytes:
|
def _wav_header(sr: int, data_len: int | None = None) -> bytes:
|
||||||
"""Streaming WAV header with unknown length (0xFFFFFFFF sizes)."""
|
"""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 (
|
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"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.
|
# workload); concurrent callers queue rather than corrupt conditionals.
|
||||||
with engine.lock:
|
with engine.lock:
|
||||||
engine._prepare(voice_path, exaggeration=req.exaggeration)
|
engine._prepare(voice_path, exaggeration=req.exaggeration)
|
||||||
if req.format == "wav":
|
|
||||||
yield _wav_header(engine.sr)
|
|
||||||
|
|
||||||
t_req = time.perf_counter()
|
t_req = time.perf_counter()
|
||||||
first_audio_ms: float | None = None
|
first_audio_ms: float | None = None
|
||||||
|
|
||||||
if not req.stream:
|
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)
|
wav, audio_sec = engine.generate(req.text, req)
|
||||||
first_audio_ms = (time.perf_counter() - t_req) * 1000
|
first_audio_ms = (time.perf_counter() - t_req) * 1000
|
||||||
log.info("oneshot: %.0fms gen, %.2fs audio", first_audio_ms, audio_sec)
|
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
|
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]:
|
def _gen(text: str) -> tuple[torch.Tensor, float]:
|
||||||
return engine.generate(text, req)
|
return engine.generate(text, req)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user