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 ") } 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()) } } }