Skip to content

Commit a59b802

Browse files
committed
Example code for SDL
1 parent e6d5a65 commit a59b802

File tree

6 files changed

+114
-44
lines changed

6 files changed

+114
-44
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/llgcode/draw2d v0.0.0-20240627062922-0ed1ff131195
1313
github.com/mutablelogic/go-client v1.0.8
1414
github.com/stretchr/testify v1.9.0
15+
github.com/veandco/go-sdl2 v0.4.40
1516
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
1617
)
1718

pkg/ffmpeg/decoder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"syscall"
88

99
// Packages
10+
1011
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
1112
)
1213

pkg/ffmpeg/frame.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Frame ff.AVFrame
1919

2020
const (
2121
PTS_UNDEFINED = ff.AV_NOPTS_VALUE
22+
TS_UNDEFINED = -1.0
2223
)
2324

2425
///////////////////////////////////////////////////////////////////////////////
@@ -92,6 +93,42 @@ func (frame *Frame) MakeWritable() error {
9293
return ff.AVUtil_frame_make_writable((*ff.AVFrame)(frame))
9394
}
9495

96+
// Make a copy of the frame, which should be released by the caller
97+
func (frame *Frame) Copy() (*Frame, error) {
98+
copy := ff.AVUtil_frame_alloc()
99+
if copy == nil {
100+
return nil, errors.New("failed to allocate frame")
101+
}
102+
switch frame.Type() {
103+
case media.AUDIO:
104+
copy.SetSampleFormat(frame.SampleFormat())
105+
copy.SetChannelLayout(frame.ChannelLayout())
106+
copy.SetSampleRate(frame.SampleRate())
107+
copy.SetNumSamples(frame.NumSamples())
108+
case media.VIDEO:
109+
copy.SetPixFmt(frame.PixelFormat())
110+
copy.SetWidth(frame.Width())
111+
copy.SetHeight(frame.Height())
112+
copy.SetSampleAspectRatio(frame.SampleAspectRatio())
113+
default:
114+
ff.AVUtil_frame_free(copy)
115+
return nil, errors.New("invalid codec type")
116+
}
117+
if err := ff.AVUtil_frame_get_buffer(copy, false); err != nil {
118+
ff.AVUtil_frame_free(copy)
119+
return nil, err
120+
}
121+
if err := ff.AVUtil_frame_copy(copy, (*ff.AVFrame)(frame)); err != nil {
122+
ff.AVUtil_frame_free(copy)
123+
return nil, err
124+
}
125+
if err := ff.AVUtil_frame_copy_props(copy, (*ff.AVFrame)(frame)); err != nil {
126+
ff.AVUtil_frame_free(copy)
127+
return nil, err
128+
}
129+
return (*Frame)(copy), nil
130+
}
131+
95132
// Unreference frame buffers
96133
func (frame *Frame) Unref() {
97134
ff.AVUtil_frame_unref((*ff.AVFrame)(frame))
@@ -199,17 +236,17 @@ func (frame *Frame) IncPts(v int64) {
199236
(*ff.AVFrame)(frame).SetPts((*ff.AVFrame)(frame).Pts() + v)
200237
}
201238

202-
// Return the timestamp in seconds, or PTS_UNDEFINED if the timestamp
239+
// Return the timestamp in seconds, or TS_UNDEFINED if the timestamp
203240
// is undefined or timebase is not set
204241
func (frame *Frame) Ts() float64 {
205242
ctx := (*ff.AVFrame)(frame)
206243
pts := ctx.Pts()
207244
if pts == ff.AV_NOPTS_VALUE {
208-
return PTS_UNDEFINED
245+
return TS_UNDEFINED
209246
}
210247
tb := ctx.TimeBase()
211248
if tb.Num() == 0 || tb.Den() == 0 {
212-
return PTS_UNDEFINED
249+
return TS_UNDEFINED
213250
}
214251
return ff.AVUtil_rational_q2d(tb) * float64(pts)
215252
}

pkg/ffmpeg/packet.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ func (packet *Packet) String() string {
2323
///////////////////////////////////////////////////////////////////////////////
2424
// PUBLIC METHODS
2525

26-
// Return the timestamp in seconds, or PTS_UNDEFINED if the timestamp
26+
// Return the timestamp in seconds, or TS_UNDEFINED if the timestamp
2727
// is undefined or timebase is not set
2828
func (packet *Packet) Ts() float64 {
2929
if packet == nil {
30-
return PTS_UNDEFINED
30+
return TS_UNDEFINED
3131
}
3232
if pts := (*ff.AVPacket)(packet).Pts(); pts == ff.AV_NOPTS_VALUE {
33-
return PTS_UNDEFINED
33+
return TS_UNDEFINED
3434
} else if tb := (*ff.AVPacket)(packet).TimeBase(); tb.Num() == 0 || tb.Den() == 0 {
35-
return PTS_UNDEFINED
35+
return TS_UNDEFINED
3636
} else {
3737
return ff.AVUtil_rational_q2d(tb) * float64(pts)
3838
}

pkg/sdl/sdl.go

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ package main
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"log"
87
"os"
98
"path/filepath"
109
"runtime"
1110
"sync"
1211
"time"
12+
"unsafe"
1313

1414
// Packages
15-
"github.com/mutablelogic/go-media"
16-
"github.com/mutablelogic/go-media/pkg/ffmpeg"
15+
media "github.com/mutablelogic/go-media"
16+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
1717
sdl "github.com/veandco/go-sdl2/sdl"
1818
)
1919

@@ -41,12 +41,6 @@ func (s *Context) Close() error {
4141
return nil
4242
}
4343

44-
func (s *Context) PushQuitEvent() {
45-
sdl.PushEvent(&sdl.QuitEvent{
46-
Type: sdl.QUIT,
47-
})
48-
}
49-
5044
func (s *Context) NewWindow(title string, width, height int32) (*Window, error) {
5145
window, err := sdl.CreateWindow(
5246
title,
@@ -67,7 +61,6 @@ func (s *Context) NewWindow(title string, width, height int32) (*Window, error)
6761
window.Destroy()
6862
return nil, err
6963
}
70-
7164
return &Window{window, renderer, texture}, nil
7265
}
7366

@@ -82,6 +75,7 @@ func (w *Window) Close() error {
8275
if err := (*sdl.Window)(w.Window).Destroy(); err != nil {
8376
result = errors.Join(result, err)
8477
}
78+
w.Texture = nil
8579
w.Renderer = nil
8680
w.Window = nil
8781

@@ -109,26 +103,63 @@ func (w *Window) RenderFrame(frame *ffmpeg.Frame) error {
109103
)
110104
}
111105

112-
func (s *Context) RunLoop() {
106+
func (s *Context) RunLoop(w *Window, evt uint32) {
113107
runtime.LockOSThread()
114108
running := true
109+
110+
pts := ffmpeg.TS_UNDEFINED
115111
for running {
116112
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
117113
switch event.(type) {
118114
case *sdl.QuitEvent:
119115
running = false
120116
break
117+
case *sdl.UserEvent:
118+
if event.(*sdl.UserEvent).Type != evt {
119+
break
120+
}
121+
122+
// Get the video frame - if nil, then end of stream
123+
frame := (*ffmpeg.Frame)(event.(*sdl.UserEvent).Data1)
124+
if frame == nil {
125+
running = false
126+
break
127+
}
128+
129+
// Pause to present the frame at the correct PTS
130+
if pts != ffmpeg.TS_UNDEFINED && pts < frame.Ts() {
131+
pause := frame.Ts() - pts
132+
if pause > 0 {
133+
sdl.Delay(uint32(pause * 1000))
134+
}
135+
}
136+
137+
// Set current timestamp
138+
pts = frame.Ts()
139+
140+
// Render the frame, release the frame resources
141+
if err := w.RenderFrame(frame); err != nil {
142+
log.Print(err)
143+
} else if err := w.Flush(); err != nil {
144+
log.Print(err)
145+
} else if err := frame.Close(); err != nil {
146+
log.Print(err)
147+
}
148+
121149
}
122150
}
123151
}
124152
}
125153

126154
func main() {
127-
sdl, err := NewSDL()
155+
ctx, err := NewSDL()
128156
if err != nil {
129157
log.Fatal(err)
130158
}
131-
defer sdl.Close()
159+
defer ctx.Close()
160+
161+
// Register an event for a new frame
162+
evt := sdl.RegisterEvents(1)
132163

133164
// Open video
134165
input, err := ffmpeg.Open(os.Args[1])
@@ -151,24 +182,28 @@ func main() {
151182
return nil, nil
152183
}
153184

154-
ch := make(chan *ffmpeg.Frame)
155-
156185
wg.Add(1)
157186
go func() {
158187
defer wg.Done()
159188
err := input.Decode(context.Background(), mapfn, func(stream int, frame *ffmpeg.Frame) error {
160-
ch <- frame
189+
copy, err := frame.Copy()
190+
if err != nil {
191+
copy.Close()
192+
return err
193+
}
194+
sdl.PushEvent(&sdl.UserEvent{
195+
Type: evt,
196+
Data1: unsafe.Pointer(copy),
197+
})
161198
return nil
162199
})
163200
if err != nil {
164201
result = errors.Join(result, err)
165202
}
166-
167-
// Close channel
168-
close(ch)
169-
170203
// Quit event
171-
sdl.PushQuitEvent()
204+
sdl.PushEvent(&sdl.QuitEvent{
205+
Type: sdl.QUIT,
206+
})
172207
}()
173208

174209
// HACK
@@ -183,27 +218,15 @@ func main() {
183218
title = meta[0].Value()
184219
}
185220

186-
window, err := sdl.NewWindow(title, w, h)
221+
// Create a new window
222+
window, err := ctx.NewWindow(title, w, h)
187223
if err != nil {
188224
log.Fatal(err)
189225
}
190226
defer window.Close()
191227

192-
wg.Add(1)
193-
go func() {
194-
defer wg.Done()
195-
for frame := range ch {
196-
if err := window.RenderFrame(frame); err != nil {
197-
fmt.Println("Error rendering frame:", err)
198-
}
199-
if err := window.Flush(); err != nil {
200-
fmt.Println("Error flushing frame:", err)
201-
}
202-
}
203-
}()
204-
205-
// Run the SDL loop
206-
sdl.RunLoop()
228+
// Run the SDL loop until quit
229+
ctx.RunLoop(window, evt)
207230

208231
// Wait until all goroutines have finished
209232
wg.Wait()

sys/ffmpeg61/avutil_frame.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ func AVUtil_frame_get_num_planes(frame *AVFrame) int {
150150
return 0
151151
}
152152

153+
// Copy frame data
154+
func AVUtil_frame_copy(dst, src *AVFrame) error {
155+
if ret := AVError(C.av_frame_copy((*C.struct_AVFrame)(dst), (*C.struct_AVFrame)(src))); ret < 0 {
156+
return ret
157+
}
158+
return nil
159+
}
160+
153161
// Copy only "metadata" fields from src to dst, those fields that do not affect the data layout in the buffers.
154162
// E.g. pts, sample rate (for audio) or sample aspect ratio (for video), but not width/height or channel layout.
155163
// Side data is also copied.

0 commit comments

Comments
 (0)