Skip to content

Make the first streaming audio packet configurable - #22

Merged
andimarafioti merged 2 commits into
mainfrom
fix/first-stream-packet
Sep 24, 2026
Merged

andimarafioti merged 2 commits into
mainfrom
fix/first-stream-packet

Conversation

@andimarafioti

@andimarafioti andimarafioti commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #20.

QwenTTS.stream() now yields a four-frame first packet by default (320 ms of audio), configurable with first_chunk_frames=1, 2, 4, or 8. Later packets independently use codec_chunk_sec, rounded to codec frames, defaulting to 0.64 seconds / eight frames to match the native steady-state width. Explicit values remain configurable. The complete default sequence, including its short tail, has a regression test. Successful completion flushes a short tail; errors and cancellation discard unfinished packets.

The pinned native decoder ignores streaming codec_chunk_sec and emits a fixed 1→2→4→8-frame ramp. This change assembles Python packets from those callbacks without changing the native revision or ctypes ABI. It does not change native callback scheduling: the four-frame first packet waits until seven native frames have arrived, and the eight-frame packet waits until fifteen. The README documents this latency limit and separate native-callback versus Python-packet profiling fields.

Adds a repeatable local benchmark and focused tests for exact packet boundaries, independent later sizing, PCM preservation after native buffers are reused, short utterances, failure propagation, repeated streaming, and cancellation.

Validation on Apple M3 Pro / Metal, macOS 26.6.2, Python 3.12.13:

  • All 64 tests pass, including ctypes sizes/offsets against the pinned C header and loading the local native library.
  • Real synthesis with Q8_0 1.7B Base talker, Q8_0 codec, and cached speaker embedding: one warm-up and three measured runs per setting with rotated order. All runs preserved 115 frames of audio, with eight-frame later packets and a short final tail.
  • A three-frame token-limited utterance flushes as one packet when eight frames are requested. Closing a live stream cancels native generation in 36.5 ms; subsequent synthesis on the same context succeeds.
First frames Median native callback Median first Python packet Packet audio
1 94.4 ms 94.5 ms 80 ms
4 84.2 ms 369.8 ms 320 ms
8 85.7 ms 826.4 ms 640 ms

The actual API defaults were also tested on Metal without either packet-size argument: after warm-up, two default runs and two explicit codec_chunk_sec=1.0 runs produced identical PCM (220,800 samples / 115 frames). Per-packet arrival times gave largest calculated gaps of 65–85 ms with the default eight-frame later packets, versus 438–557 ms with thirteen-frame packets. Default first yields were 420–439 ms. This calculation assumes immediate continuous playback from the first packet; it is not a listening test or a guarantee of gap-free playback.

No version bump, native libraries, model weights, or build artifacts are included.

@andimarafioti
andimarafioti merged commit 87f23de into main Sep 24, 2026
1 check passed
@andimarafioti
andimarafioti deleted the fix/first-stream-packet branch September 24, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make the first streamed audio packet configurable and larger than one frame by default

1 participant