feat(audio): Audio-Sinks oto und null mit eigener WAV-Dekodierung
Sink-Vertrag Play/Name; oto-Sink (github.com/ebitengine/oto/v3, auf v3.5.0 gepinnt — aktuelle stabile Major-Version; purego statt CGO, damit cross-compilierbar) spielt RIFF/WAVE-PCM blockierend, ein Context pro PCM-Format gecacht, da oto nur einen Kontext pro Prozess erlaubt. null-Sink verwirft (CI/Server/Tests) und loggt optional. WAV-Parser (RIFF-Chunks, PCM 8/16 Bit, Float32) mit klarer Fehler für alles andere. Echte Wiedergabe nur hinter TALK_TEST_REAL_AUDIO=1.
This commit is contained in:
1 parent
5f450ea015
commit
523e6f4bf2
7 files changed
+439
-4
No files matched your search
@@ -0,0 +1,55 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Sink-Namen (Teil des öffentlichen Vertrags; Spec: audio.sink = "oto"|"null").
|
||||
const (
|
||||
NameOto = "oto"
|
||||
NameNull = "null"
|
||||
)
|
||||
|
||||
// Sink gibt Audio-Bytes wieder. Die Bytes stammen aus Provider.Synthesize;
|
||||
// oto versteht RIFF/WAVE (PCM) — das Lieferformat von osbuiltin.
|
||||
type Sink interface {
|
||||
// Play spielt die Daten blockierend ab (bis zum Ende oder bis ctx
|
||||
// abgebrochen wird).
|
||||
Play(ctx context.Context, data []byte) error
|
||||
|
||||
// Name liefert den Sink-Namen (audio.sink-Konfiguration).
|
||||
Name() string
|
||||
}
|
||||
|
||||
// NewSink liefert den Sink zum konfigurierten Namen ("oto" oder "null").
|
||||
// Unbekannte Namen sind ein klarer Fehler.
|
||||
func NewSink(name string) (Sink, error) {
|
||||
switch name {
|
||||
case NameOto:
|
||||
return OtoSink{}, nil
|
||||
case NameNull:
|
||||
return NullSink{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unbekannter audio sink %q (erlaubt: %q, %q)", name, NameOto, NameNull)
|
||||
}
|
||||
}
|
||||
|
||||
// NullSink verwirft jede Ansage ohne Ton — für CI, Server und Tests. Wurde
|
||||
// ein W gesetzt, loggt Play eine Zeile darüber (der CLI-Pfad hängt stderr
|
||||
// an, damit „läuft ohne Ton durch" auch sichtbar bleibt).
|
||||
type NullSink struct {
|
||||
W io.Writer // optionaler Log-Output; nil = still verwerfen
|
||||
}
|
||||
|
||||
// Name implementiert Sink.
|
||||
func (NullSink) Name() string { return NameNull }
|
||||
|
||||
// Play implementiert Sink: verwirft die Daten.
|
||||
func (n NullSink) Play(ctx context.Context, data []byte) error {
|
||||
if n.W != nil {
|
||||
fmt.Fprintf(n.W, "audio: %d Bytes verworfen (null sink)\n", len(data))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user