Files
LocalAI/backend/go/vibevoice-cpp/vibevoicecpp_stream_test.go
LocalAI [bot] 3c2d85aae4 feat(vibevoice-cpp): true streaming TTS (TTSStream via vv_capi_tts_stream) (#10764)
* feat(vibevoice-cpp): true streaming TTSStream via vv_capi_tts_stream

Replaces the synth-to-tempfile TTSStream hack with a real streaming path:
binds the new vv_capi_tts_stream callback ABI via a single reusable purego
callback (CGO_ENABLED=0-safe, no runtime/cgo), copies each int16 PCM window
into the gRPC results channel after the streaming WAV header.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]

* test(vibevoice-cpp): real-model streaming integration test with TTFA measurement

Gated behind VIBEVOICE_IT=1, this Ginkgo spec dlopens the engine .so and
drives the exact Go->purego->C TTSStream/TTS path against the real
vibevoice-realtime-0.5B model. It measures time-to-first-audio for the
streaming path versus the batch path and asserts the streaming win:
44-byte WAV header first, >=2 PCM windows, non-silent audio, and
TTFA < total_stream. Without the env var the spec skips so CI and
normal go test are unaffected.

Measured: TTFA 2.38s vs batch deliver-time 39.96s (first audio in 5.9%
of the batch time, ~17x faster), 18 stream chunks, non-silent 24kHz PCM.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]

* chore(vibevoice-cpp): pin streaming-decoder engine build

Bumps VIBEVOICE_CPP_VERSION to the streaming-decoder engine commit that
adds vv_capi_tts_stream (localai-org/vibevoice.cpp#8). Re-pin to the
merged master commit once that PR lands.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]

* chore(vibevoice-cpp): re-pin to merged streaming-decoder commit

localai-org/vibevoice.cpp#8 merged to master as 000e372; move the pin
off the PR branch commit onto the merged master commit.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]

* test(vibevoice-cpp): check writer errors in TTFA report (errcheck)

golangci-lint errcheck flagged the unchecked fmt.Fprintf calls that
print the streaming TTFA headline. Build the report once with
fmt.Sprintf and write it per destination with an explicitly discarded
error, matching the GinkgoWriter reporting idiom used by the other
backend tests.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-fable-5 [Claude Code]

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
2026-07-09 23:48:44 +00:00

47 lines
1.4 KiB
Go

package main
import (
"encoding/binary"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("TTSStream callback framing", func() {
// The real C callback copies a transient int16 PCM buffer out of the
// engine into a fresh []byte and pushes it onto the active stream's
// results channel as little-endian bytes. deliverPCMForTest runs that
// exact copy-and-push path against a []int16 so we can validate the
// framing without the C library (full audio e2e is a later task).
It("copies int16 PCM from the C callback into the results channel as LE bytes", func() {
samples := []int16{0, 1, -1, 32767, -32768, 1234, -4321}
prev := activeStream
DeferCleanup(func() { activeStream = prev })
s := &streamState{results: make(chan []byte, 1)}
activeStream = s
deliverPCMForTest(samples)
var got []byte
Eventually(s.results).Should(Receive(&got))
Expect(got).To(HaveLen(len(samples) * 2))
want := make([]byte, len(samples)*2)
for i, v := range samples {
binary.LittleEndian.PutUint16(want[i*2:], uint16(v))
}
Expect(got).To(Equal(want))
})
It("is a no-op when there is no active stream", func() {
prev := activeStream
DeferCleanup(func() { activeStream = prev })
activeStream = nil
// Must not panic when no stream is installed.
Expect(func() { deliverPCMForTest([]int16{1, 2, 3}) }).ToNot(Panic())
})
})