diff --git a/cmd/mxl-audio/main.go b/cmd/mxl-audio/main.go deleted file mode 100644 index d1bde61..0000000 --- a/cmd/mxl-audio/main.go +++ /dev/null @@ -1,144 +0,0 @@ -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()) - } - } -} diff --git a/cmd/mxl-reader/main.go b/cmd/mxl-reader/main.go deleted file mode 100644 index d7a1236..0000000 --- a/cmd/mxl-reader/main.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "mxl-player/internal/source" - "os" - "os/signal" - "syscall" - "time" -) - -func main() { - domain := flag.String("domain", "/dev/shm/mxl", "MXL domain") - flowID := flag.String("flow", "5fbec3b1-1b0f-417d-9059-8b94a47197ed", "Flow UUID") - timeout := flag.Duration("timeout", 200*time.Millisecond, "Per-grain read timeout") - count := flag.Int("count", 10, "Stop after N grains (0 = run forever)") - flag.Parse() - - src, err := source.Open(*domain, *flowID) - if err != nil { - log.Fatal(err) - } - defer src.Close() - fmt.Printf("format=%s rate=%d/%d stride=%d grainCount=%d\n", - src.Format(), - src.Rate().Num, - src.Rate().Den, - src.Stride(), - src.GrainCount()) - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - - grains := 0 - for { - select { - case <-stop: - fmt.Printf("\nstopping: %d grains read\n", grains) - return - default: - } - - f, err := src.Next(*timeout) - if err != nil { - fmt.Fprintln(os.Stderr, err) - return - } - lines := uint32(0) - if src.Stride() > 0 { - lines = f.Size / src.Stride() - } - fmt.Printf("idx=%d size=%d (%dx%d, %d lines) invalid=%v\n", - f.Index, f.Size, f.Width, f.Height, lines, f.Invalid) - grains++ - if *count > 0 && grains >= *count { - fmt.Printf("done: %d grains read\n", grains) - return - } - } -} diff --git a/cmd/mxl-sync/main.go b/cmd/mxl-sync/main.go deleted file mode 100644 index 0665380..0000000 --- a/cmd/mxl-sync/main.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -import ( - "errors" - "flag" - "fmt" - "log" - "os" - "os/signal" - "syscall" - "time" - - "github.com/qvest-digital/go-mxl/mxl" -) - -func main() { - domain := flag.String("d", "/dev/shm/mxl", "MXL domain") - videoFlow := flag.String("v", "5fbec3b1-1b0f-417d-9059-8b94a47197ed", "Video flow UUID") - audioFlow := flag.String("a", "5fbec3b1-1b0f-417d-9059-8b94a47197ec", "Audio flow UUID") - flag.Parse() - if *videoFlow == "" || *audioFlow == "" { - log.Fatal("need both -v and -a ") - } - - inst, err := mxl.NewInstance(*domain, "") - if err != nil { - log.Fatal(err) - } - defer inst.Close() - - vr, err := inst.NewReader(*videoFlow) - if err != nil { - log.Fatal(err) - } - defer vr.Close() - - ar, err := inst.NewReader(*audioFlow) - if err != nil { - log.Fatal(err) - } - defer ar.Close() - - vInfo, _ := vr.Info() - aInfo, _ := ar.Info() - vRate := vInfo.Config.Common.GrainRate - aRate := aInfo.Config.Common.GrainRate - aChans := aInfo.Config.Continuous.ChannelCount - fmt.Printf("video: %dx%d %d/%d | audio: %dch %d/%d\n", - vInfo.Config.Discrete.SliceSizes[0], - vInfo.Config.Discrete.GrainCount, - vRate.Num, vRate.Den, - aChans, aRate.Num, aRate.Den) - - group, err := inst.NewSyncGroup() - if err != nil { - log.Fatal(err) - } - defer group.Close() - if err := group.AddReader(vr); err != nil { - log.Fatal(err) - } - if err := group.AddReader(ar); err != nil { - log.Fatal(err) - } - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - - idx := mxl.CurrentIndex(vRate) - audioBatch := uint64(aRate.Num / (100 * aRate.Den)) // ~10ms - if audioBatch == 0 { - audioBatch = 1 - } - - var ticks int - for { - select { - case <-stop: - fmt.Printf("\nstopped after %d ticks\n", ticks) - return - default: - } - - ts := mxl.IndexToTimestamp(vRate, idx) - err := group.WaitForDataAt(ts, 500*time.Millisecond) - switch { - case err == nil: - // Read video grain - g, gerr := vr.GetGrain(idx, 50*time.Millisecond) - // Read audio samples at the same timestamp - aIdx := mxl.TimestampToIndex(aRate, ts) - _, aerr := ar.GetSamples(aIdx, int(audioBatch), 50*time.Millisecond) - if gerr != nil { - log.Printf("grain: %v", gerr) - } else if aerr != nil { - log.Printf("samples: %v", aerr) - } else { - fmt.Printf("tick idx=%d ts=%d grainSize=%d audioIdx=%d\n", - idx, ts, g.GrainSize, aIdx) - ticks++ - if ticks >= 10 { - fmt.Println("done") - return - } - } - idx++ - case errors.Is(err, mxl.ErrTimeout), errors.Is(err, mxl.ErrOutOfRangeEarly): - time.Sleep(5 * time.Millisecond) - case errors.Is(err, mxl.ErrOutOfRangeLate): - log.Printf("fell behind, resyncing") - idx = mxl.CurrentIndex(vRate) - default: - log.Fatalf("WaitForDataAt: %v", err) - } - } -}