145 lines
3.2 KiB
Go
145 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"mxl-player/internal/sdl"
|
|
"mxl-player/internal/source"
|
|
)
|
|
|
|
func main() {
|
|
domain := flag.String("d", "/dev/shm/mxl", "MXL domain")
|
|
flowID := flag.String("a", "", "MXL audio flow UUID")
|
|
list := flag.Bool("l", false, "List playback audio devices and exit")
|
|
audioDeviceId := flag.Uint("ad", uint(sdl.AudioDeviceDefaultPlayback), "Audio device SDL id")
|
|
flag.Parse()
|
|
|
|
if err := sdl.Load(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if !sdl.Init(sdl.InitVideo | sdl.InitAudio) {
|
|
log.Fatalf("SDL_Init: %s", sdl.GetError())
|
|
}
|
|
defer sdl.Quit()
|
|
|
|
if *list {
|
|
for _, d := range sdl.GetAudioPlaybackDevices() {
|
|
fmt.Printf("%d: %s\n", d.ID, d.Name)
|
|
}
|
|
return
|
|
}
|
|
if *flowID == "" {
|
|
log.Fatal("missing -a <audio-flow-uuid>")
|
|
}
|
|
|
|
src, err := source.OpenAudio(*domain, *flowID)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer src.Close()
|
|
|
|
rate := src.Rate()
|
|
chans := src.Channels()
|
|
fmt.Printf("audio: %dch %d/%d Hz\n", chans, rate.Num, rate.Den)
|
|
|
|
stream := sdl.OpenAudioDeviceStream(uint32(*audioDeviceId), sdl.AudioSpec{
|
|
Format: sdl.AudioF32,
|
|
Channels: int32(chans),
|
|
Freq: int32(rate.Num / rate.Den),
|
|
})
|
|
if stream == 0 {
|
|
log.Fatalf("OpenAudioDeviceStream: %s", sdl.GetError())
|
|
}
|
|
defer sdl.DestroyAudioStream(stream)
|
|
|
|
if !sdl.ResumeAudioStreamDevice(stream) {
|
|
log.Fatalf("ResumeAudioStreamDevice: %s", sdl.GetError())
|
|
}
|
|
|
|
// ~10ms batch: sampleRate / 100
|
|
batch := uint64(rate.Num / (100 * rate.Den))
|
|
if batch == 0 {
|
|
batch = 1
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
stop := make(chan os.Signal, 1)
|
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
<-stop
|
|
cancel()
|
|
}()
|
|
|
|
var debugCount int
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
fmt.Println("\nstopped")
|
|
return
|
|
default:
|
|
}
|
|
|
|
// Backpressure: if SDL has > 200ms buffered, wait for it to drain.
|
|
queued := sdl.GetAudioStreamQueued(stream)
|
|
maxQueued := int32(rate.Num/(100*rate.Den)) * int32(chans) * 4 * 20 // 200ms
|
|
if queued > maxQueued {
|
|
time.Sleep(10 * time.Millisecond)
|
|
continue
|
|
}
|
|
|
|
f, err := src.NextAudio(ctx, batch, 20*time.Millisecond)
|
|
if err != nil {
|
|
if ctx.Err() != nil {
|
|
fmt.Println("\nstopped")
|
|
return
|
|
}
|
|
log.Printf("audio read: %v", err)
|
|
continue
|
|
}
|
|
|
|
// Debug: scan for non-zero samples
|
|
if debugCount < 5 {
|
|
sizes := make([]int, len(f.Samples))
|
|
nonZero := 0
|
|
for i, s := range f.Samples {
|
|
sizes[i] = len(s)
|
|
for _, b := range s {
|
|
if b != 0 {
|
|
nonZero++
|
|
}
|
|
}
|
|
}
|
|
fmt.Printf("read idx=%d batch=%d sampleSizes=%v queued=%d nonZeroBytes=%d\n",
|
|
f.Index, batch, sizes, queued, nonZero)
|
|
debugCount++
|
|
}
|
|
|
|
// Interleave per-channel Float32 into a single buffer.
|
|
frameBytes := int(batch) * int(chans) * 4
|
|
interleaved := make([]byte, frameBytes)
|
|
for ch := uint64(0); ch < chans; ch++ {
|
|
srcBytes := f.Samples[ch]
|
|
for i := uint64(0); i < batch; i++ {
|
|
srcOff := i * 4
|
|
dstOff := (i*chans + ch) * 4
|
|
if srcOff+4 <= uint64(len(srcBytes)) {
|
|
copy(interleaved[dstOff:dstOff+4], srcBytes[srcOff:srcOff+4])
|
|
}
|
|
}
|
|
}
|
|
|
|
if !sdl.PutAudioStreamData(stream, interleaved) {
|
|
log.Printf("PutAudioStreamData: %s", sdl.GetError())
|
|
}
|
|
}
|
|
}
|