GUI, but still shit a bit
This commit is contained in:
+192
-120
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
cimgui "github.com/AllenDang/cimgui-go/imgui"
|
||||
vk "github.com/christerso/vulkan-go/vk"
|
||||
"github.com/qvest-digital/go-mxl/mxl"
|
||||
pflag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@@ -78,8 +77,10 @@ func checkMXLargs(args appArgs) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// video: 5fbec3b1-1b0f-417d-9059-8b94a47197ed
|
||||
// audio: 5fbec3b1-1b0f-417d-9059-8b94a47197ec
|
||||
// timelapse video: 5fbec3b1-1b0f-417d-9059-8b94a47197ed
|
||||
// timelapse audio: 5fbec3b1-1b0f-417d-9059-8b94a47197ec
|
||||
// f1 video: 5fbec3b1-1b0f-417d-9059-8b94a47197ef
|
||||
// f1 audio: 5fbec3b1-1b0f-417d-9059-8b94a47197eb
|
||||
var args appArgs
|
||||
flagSet := pflag.NewFlagSet(APP_NAME, pflag.ContinueOnError)
|
||||
flagSet.Usage = func() { printUsage(os.Stderr) }
|
||||
@@ -259,7 +260,6 @@ func main() {
|
||||
fmt.Printf("sync: video %dx%d audio %dch batch=%d\n",
|
||||
syncSrc.Width(), syncSrc.Height(), aChans, audioBatch)
|
||||
case args.VideoFlowId != "":
|
||||
|
||||
videoSrc, err = source.Open(args.Domain, args.VideoFlowId)
|
||||
if err != nil {
|
||||
log.Fatalf("source: %v", err)
|
||||
@@ -338,68 +338,98 @@ func main() {
|
||||
defer vkDevice.WaitIdle()
|
||||
}
|
||||
|
||||
// GUI state (accessible from doReconnect + goroutine)
|
||||
var (
|
||||
domainStr string = args.Domain
|
||||
videoStr string = args.VideoFlowId
|
||||
audioStr string = args.AudioFlowId
|
||||
showStats bool = true
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
grant := make(chan struct{}, 1)
|
||||
type reconnectParams struct {
|
||||
domain string
|
||||
video string
|
||||
audio string
|
||||
}
|
||||
// One control channel: grant (empty params) or reconnect (with params).
|
||||
control := make(chan reconnectParams, 1)
|
||||
staged := make(chan uint64)
|
||||
failed := make(chan struct{})
|
||||
|
||||
reopen := func() error {
|
||||
reopen := func(params reconnectParams) error {
|
||||
// Close current sources
|
||||
if syncSrc != nil {
|
||||
_ = syncSrc.Close()
|
||||
syncSrc = nil
|
||||
}
|
||||
if videoSrc != nil {
|
||||
_ = videoSrc.Close()
|
||||
videoSrc = nil
|
||||
}
|
||||
if audioSrc != nil {
|
||||
_ = audioSrc.Close()
|
||||
audioSrc = nil
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
if syncSrc != nil {
|
||||
s, e := source.OpenSync(args.Domain, args.VideoFlowId, args.AudioFlowId)
|
||||
if e == nil {
|
||||
if r != nil {
|
||||
newSize := vk.DeviceSize(s.Stride()) * vk.DeviceSize(s.Height())
|
||||
if newSize != r.FrameSize() {
|
||||
if e = r.RecreateBuffers(newSize); e != nil {
|
||||
return e
|
||||
}
|
||||
// Try once. Return error if fails — caller loops back to select
|
||||
// and can pick up new reconnect params or a new grant.
|
||||
if params.video != "" && params.audio != "" {
|
||||
s, e := source.OpenSync(params.domain, params.video, params.audio)
|
||||
if e == nil {
|
||||
if r != nil {
|
||||
newSize := vk.DeviceSize(s.Stride()) * vk.DeviceSize(s.Height())
|
||||
if newSize != r.FrameSize() {
|
||||
if e = r.RecreateBuffers(newSize); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
syncSrc = s
|
||||
return nil
|
||||
}
|
||||
log.Printf("source: reopen retry: %v", e)
|
||||
} else if videoSrc != nil {
|
||||
s, e := source.Open(args.Domain, args.VideoFlowId)
|
||||
if e == nil {
|
||||
if r != nil {
|
||||
newSize := vk.DeviceSize(s.Stride()) * vk.DeviceSize(s.Height())
|
||||
if newSize != r.FrameSize() {
|
||||
if e = r.RecreateBuffers(newSize); e != nil {
|
||||
return e
|
||||
}
|
||||
syncSrc = s
|
||||
aChans = s.Channels()
|
||||
audioBatch = uint64(s.AudioRate().Num) / uint64(s.Rate().Num)
|
||||
if audioBatch == 0 {
|
||||
audioBatch = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
} else if params.video != "" {
|
||||
s, e := source.Open(params.domain, params.video)
|
||||
if e == nil {
|
||||
if r != nil {
|
||||
newSize := vk.DeviceSize(s.Stride()) * vk.DeviceSize(s.Height())
|
||||
if newSize != r.FrameSize() {
|
||||
if e = r.RecreateBuffers(newSize); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
videoSrc = s
|
||||
return nil
|
||||
}
|
||||
log.Printf("source: reopen retry: %v", e)
|
||||
} else if audioSrc != nil {
|
||||
s, e := source.OpenAudio(args.Domain, args.AudioFlowId)
|
||||
if e == nil {
|
||||
audioSrc = s
|
||||
return nil
|
||||
}
|
||||
log.Printf("source: reopen retry: %v", e)
|
||||
videoSrc = s
|
||||
return nil
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
return e
|
||||
} else if params.audio != "" {
|
||||
s, e := source.OpenAudio(params.domain, params.audio)
|
||||
if e == nil {
|
||||
audioSrc = s
|
||||
aChans = s.Channels()
|
||||
audioBatch = uint64(s.Rate().Num) / (100 * uint64(s.Rate().Den))
|
||||
if audioBatch == 0 {
|
||||
audioBatch = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
return fmt.Errorf("reopen: no flow specified")
|
||||
}
|
||||
|
||||
doReconnect := func() {
|
||||
select {
|
||||
case <-control:
|
||||
default:
|
||||
}
|
||||
control <- reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -409,6 +439,25 @@ func main() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case params := <-control:
|
||||
if params.video != "" || params.audio != "" {
|
||||
if rerr := reopen(params); rerr != nil {
|
||||
if errors.Is(rerr, context.Canceled) {
|
||||
return
|
||||
}
|
||||
log.Printf("source: reopen failed: %v, retrying", rerr)
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
select {
|
||||
case control <- params:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
default:
|
||||
}
|
||||
queued := sdl.GetAudioStreamQueued(audioStream)
|
||||
@@ -426,18 +475,22 @@ func main() {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, mxl.ErrFlowInvalid) {
|
||||
log.Printf("source: flow invalid, reopening")
|
||||
if rerr := reopen(); rerr != nil {
|
||||
log.Printf("source: reopen failed: %v", rerr)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
log.Printf("source: %v", err)
|
||||
cancel()
|
||||
return
|
||||
params := reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}
|
||||
select {
|
||||
case <-control:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
select {
|
||||
case control <- params:
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
if f.Samples != nil && audioStream != 0 {
|
||||
sdl.PutAudioStreamData(audioStream, interleaveAudio(f.Samples))
|
||||
@@ -447,10 +500,29 @@ func main() {
|
||||
|
||||
// Video (with or without sync) mode: grant/staged handshake.
|
||||
for {
|
||||
select {
|
||||
case <-grant:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
params := <-control
|
||||
if params.video != "" || params.audio != "" {
|
||||
// Reconnect request from Connect button or auto-retry.
|
||||
select {
|
||||
case <-control: // drain any pending grant
|
||||
default:
|
||||
}
|
||||
if rerr := reopen(params); rerr != nil {
|
||||
if errors.Is(rerr, context.Canceled) {
|
||||
return
|
||||
}
|
||||
log.Printf("source: reopen failed: %v, retrying", rerr)
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
select {
|
||||
case control <- params:
|
||||
default:
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var payload []byte
|
||||
@@ -462,23 +534,18 @@ func main() {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, mxl.ErrFlowInvalid) {
|
||||
log.Printf("source: flow invalid, reopening")
|
||||
if rerr := reopen(); rerr != nil {
|
||||
log.Printf("source: reopen failed: %v", rerr)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
select {
|
||||
case failed <- struct{}{}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
log.Printf("source: %v", err)
|
||||
cancel()
|
||||
return
|
||||
// Drain any pending grant, then send reconnect.
|
||||
select {
|
||||
case <-control:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case control <- reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
payload = vFrame.Payload
|
||||
grainIdx = vFrame.Index
|
||||
@@ -492,27 +559,20 @@ func main() {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, mxl.ErrFlowInvalid) {
|
||||
log.Printf("source: flow invalid, reopening")
|
||||
if rerr := reopen(); rerr != nil {
|
||||
log.Printf("source: reopen failed: %v", rerr)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
select {
|
||||
case failed <- struct{}{}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
log.Printf("source: %v", err)
|
||||
cancel()
|
||||
return
|
||||
select {
|
||||
case <-control:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case control <- reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
payload = f.Payload
|
||||
grainIdx = f.Index
|
||||
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
@@ -540,11 +600,6 @@ func main() {
|
||||
frameCount uint64
|
||||
lastReport time.Time
|
||||
lastFrame time.Time
|
||||
// ImGui stats
|
||||
domainStr string = args.Domain
|
||||
videoStr string = args.VideoFlowId
|
||||
audioStr string = args.AudioFlowId
|
||||
showStats bool = true
|
||||
)
|
||||
lastFrame = time.Now()
|
||||
|
||||
@@ -559,8 +614,13 @@ func main() {
|
||||
case sdl.EventWindowResized, sdl.EventPixelSizeChanged:
|
||||
resized = true
|
||||
case sdl.EventKeyDown:
|
||||
if gui.IO().WantCaptureKeyboard() {
|
||||
break
|
||||
}
|
||||
key := *(*int32)(unsafe.Pointer(&event[28]))
|
||||
switch uint32(key) {
|
||||
case sdl.KeyQ:
|
||||
fallthrough
|
||||
case sdl.KeyEscape:
|
||||
running = false
|
||||
case sdl.KeyF:
|
||||
@@ -588,7 +648,7 @@ func main() {
|
||||
}
|
||||
if !granted {
|
||||
select {
|
||||
case grant <- struct{}{}:
|
||||
case control <- reconnectParams{}:
|
||||
granted = true
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
@@ -596,35 +656,37 @@ func main() {
|
||||
}
|
||||
}
|
||||
var shownIndex uint64
|
||||
hasFrame := false
|
||||
select {
|
||||
case shownIndex = <-staged:
|
||||
granted = false
|
||||
case <-failed:
|
||||
granted = false
|
||||
continue
|
||||
hasFrame = true
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
continue
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
continue
|
||||
// No frame staged. Reset granted so we re-grant on next iteration.
|
||||
granted = false
|
||||
}
|
||||
|
||||
// stats
|
||||
if lastIndex != 0 && shownIndex > lastIndex {
|
||||
if g := shownIndex - lastIndex - 1; g > 0 {
|
||||
dropped += g
|
||||
if hasFrame {
|
||||
if lastIndex != 0 && shownIndex > lastIndex {
|
||||
if g := shownIndex - lastIndex - 1; g > 0 {
|
||||
dropped += g
|
||||
}
|
||||
}
|
||||
lastIndex = shownIndex
|
||||
frameCount++
|
||||
if now := time.Now(); now.Sub(lastReport) >= time.Second {
|
||||
dt := now.Sub(lastReport).Seconds()
|
||||
fps = float64(frameCount) / dt
|
||||
fmt.Printf("fps=%.1f dropped=%d idx=%d frameTime=%.2fms\n",
|
||||
fps, dropped, shownIndex, float64(now.Sub(frameStart).Microseconds())/1000.0)
|
||||
frameCount = 0
|
||||
dropped = 0
|
||||
lastReport = now
|
||||
}
|
||||
}
|
||||
lastIndex = shownIndex
|
||||
frameCount++
|
||||
if now := time.Now(); now.Sub(lastReport) >= time.Second {
|
||||
dt := now.Sub(lastReport).Seconds()
|
||||
fps = float64(frameCount) / dt
|
||||
fmt.Printf("fps=%.1f dropped=%d idx=%d frameTime=%.2fms\n",
|
||||
fps, dropped, shownIndex, float64(now.Sub(frameStart).Microseconds())/1000.0)
|
||||
frameCount = 0
|
||||
dropped = 0
|
||||
lastReport = now
|
||||
}
|
||||
// end of stats
|
||||
if r != nil {
|
||||
@@ -633,17 +695,24 @@ func main() {
|
||||
// cimgui.Begin("Test")
|
||||
if showStats {
|
||||
cimgui.SetNextWindowPos(cimgui.Vec2{X: 10, Y: 10})
|
||||
cimgui.SetNextWindowSize(cimgui.Vec2{X: 300, Y: 300})
|
||||
cimgui.SetNextWindowSize(cimgui.Vec2{X: 200, Y: 200})
|
||||
cimgui.BeginV("Stats", &showStats,
|
||||
cimgui.WindowFlagsNoTitleBar|cimgui.WindowFlagsNoBackground|cimgui.WindowFlagsNoResize|cimgui.WindowFlagsNoScrollbar)
|
||||
cimgui.WindowFlagsNoTitleBar|cimgui.WindowFlagsNoResize|cimgui.WindowFlagsNoScrollbar)
|
||||
cimgui.Text(fmt.Sprintf("FPS: %.1f", fps))
|
||||
cimgui.Text(fmt.Sprintf("Dropped: %d", dropped))
|
||||
cimgui.Text(fmt.Sprintf("Index: %d", shownIndex))
|
||||
if videoSrc != nil {
|
||||
cimgui.Text(fmt.Sprintf("Video: %dx%d %.2fp", videoSrc.Width(), videoSrc.Height(),
|
||||
float32(videoSrc.Rate().Num/videoSrc.Rate().Den)))
|
||||
}
|
||||
if syncSrc != nil {
|
||||
cimgui.Text(fmt.Sprintf("Video: %dx%d %.2fp", syncSrc.Width(), syncSrc.Height(),
|
||||
float32(syncSrc.Rate().Num/syncSrc.AudioRate().Den)))
|
||||
float32(syncSrc.Rate().Num/syncSrc.Rate().Den)))
|
||||
cimgui.Text(fmt.Sprintf("Audio: %dch %dkHz", syncSrc.Channels(), syncSrc.AudioRate().Num))
|
||||
}
|
||||
cimgui.Text("\nPress F1 to hide stats")
|
||||
cimgui.Text("Q or Esc to quit")
|
||||
cimgui.Text("F for fullscreen")
|
||||
cimgui.End()
|
||||
}
|
||||
cimgui.Begin("Connection")
|
||||
@@ -651,6 +720,9 @@ func main() {
|
||||
cimgui.InputTextWithHint("Video UUID", "", &videoStr, 0, nil)
|
||||
cimgui.InputTextWithHint("Audio UUID", "", &audioStr, 0, nil)
|
||||
cimgui.Checkbox("Show stats", &showStats)
|
||||
if cimgui.Button("Connect") {
|
||||
doReconnect()
|
||||
}
|
||||
cimgui.End()
|
||||
gui.EndFrame()
|
||||
lastFrame = time.Now()
|
||||
|
||||
Reference in New Issue
Block a user