Skip to content

Commit bcaa816

Browse files
committed
Added SDL routines
1 parent 744ca3a commit bcaa816

File tree

5 files changed

+234
-24
lines changed

5 files changed

+234
-24
lines changed

pkg/ffmpeg/decoder.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,9 @@ func (d *Decoder) decode(packet *ff.AVPacket, fn DecoderFrameFn) error {
152152
dest = (*Frame)(d.frame)
153153
}
154154

155-
// TODO: Modify Pts?
156-
// What else do we need to copy across?
157-
fmt.Println("TODO", d.timeBase, dest.TimeBase(), ff.AVTimestamp(dest.Pts()))
158-
if dest.Pts() == PTS_UNDEFINED {
159-
(*ff.AVFrame)(dest).SetPts(d.frame.Pts())
160-
}
155+
// Copy accross the timebase and pts
156+
(*ff.AVFrame)(dest).SetPts(d.frame.Pts())
157+
(*ff.AVFrame)(dest).SetTimeBase(d.timeBase)
161158

162159
// Pass back to the caller
163160
if err := fn(d.stream, dest); errors.Is(err, io.EOF) {

pkg/ffmpeg/encoder.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,6 @@ type Encoder struct {
2929
next_pts int64
3030
}
3131

32-
// EncoderFrameFn is a function which is called to receive a frame to encode. It should
33-
// return nil to continue encoding or io.EOF to stop encoding.
34-
type EncoderFrameFn func(int) (*Frame, error)
35-
36-
// EncoderPacketFn is a function which is called for each packet encoded, with
37-
// the stream timebase.
38-
type EncoderPacketFn func(*Packet) error
39-
4032
////////////////////////////////////////////////////////////////////////////////
4133
// LIFECYCLE
4234

pkg/ffmpeg/writer.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ type writer_callback struct {
3030
w io.Writer
3131
}
3232

33+
// EncoderFrameFn is a function which is called to receive a frame to encode. It should
34+
// return nil to continue encoding or io.EOF to stop encoding.
35+
type EncoderFrameFn func(int) (*Frame, error)
36+
37+
// EncoderPacketFn is a function which is called for each packet encoded, with
38+
// the stream timebase.
39+
type EncoderPacketFn func(*Packet) error
40+
3341
//////////////////////////////////////////////////////////////////////////////
3442
// GLOBALS
3543

pkg/sdl/sdl.go

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
"runtime"
11+
"sync"
12+
"time"
13+
14+
// Packages
15+
"github.com/mutablelogic/go-media"
16+
"github.com/mutablelogic/go-media/pkg/ffmpeg"
17+
sdl "github.com/veandco/go-sdl2/sdl"
18+
)
19+
20+
type Context struct {
21+
}
22+
23+
type Window struct {
24+
*sdl.Window
25+
*sdl.Renderer
26+
*sdl.Texture
27+
}
28+
29+
type Surface sdl.Surface
30+
31+
// Create a new SDL object which can output audio and video
32+
func NewSDL() (*Context, error) {
33+
if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
34+
return nil, err
35+
}
36+
return &Context{}, nil
37+
}
38+
39+
func (s *Context) Close() error {
40+
sdl.Quit()
41+
return nil
42+
}
43+
44+
func (s *Context) PushQuitEvent() {
45+
sdl.PushEvent(&sdl.QuitEvent{
46+
Type: sdl.QUIT,
47+
})
48+
}
49+
50+
func (s *Context) NewWindow(title string, width, height int32) (*Window, error) {
51+
window, err := sdl.CreateWindow(
52+
title,
53+
sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
54+
width, height,
55+
sdl.WINDOW_SHOWN|sdl.WINDOW_BORDERLESS)
56+
if err != nil {
57+
return nil, err
58+
}
59+
renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
60+
if err != nil {
61+
window.Destroy()
62+
return nil, err
63+
}
64+
texture, err := renderer.CreateTexture(sdl.PIXELFORMAT_IYUV, sdl.TEXTUREACCESS_STREAMING, width, height)
65+
if err != nil {
66+
renderer.Destroy()
67+
window.Destroy()
68+
return nil, err
69+
}
70+
71+
return &Window{window, renderer, texture}, nil
72+
}
73+
74+
func (w *Window) Close() error {
75+
var result error
76+
if err := (*sdl.Texture)(w.Texture).Destroy(); err != nil {
77+
result = errors.Join(result, err)
78+
}
79+
if err := (*sdl.Renderer)(w.Renderer).Destroy(); err != nil {
80+
result = errors.Join(result, err)
81+
}
82+
if err := (*sdl.Window)(w.Window).Destroy(); err != nil {
83+
result = errors.Join(result, err)
84+
}
85+
w.Renderer = nil
86+
w.Window = nil
87+
88+
// Return any errors
89+
return result
90+
}
91+
92+
func (w *Window) Flush() error {
93+
if err := w.Renderer.Copy(w.Texture, nil, nil); err != nil {
94+
return err
95+
}
96+
w.Renderer.Present()
97+
return nil
98+
}
99+
100+
func (w *Window) RenderFrame(frame *ffmpeg.Frame) error {
101+
return w.UpdateYUV(
102+
nil,
103+
frame.Bytes(0),
104+
frame.Stride(0),
105+
frame.Bytes(1),
106+
frame.Stride(1),
107+
frame.Bytes(2),
108+
frame.Stride(2),
109+
)
110+
}
111+
112+
func (s *Context) RunLoop() {
113+
runtime.LockOSThread()
114+
running := true
115+
for running {
116+
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
117+
switch event.(type) {
118+
case *sdl.QuitEvent:
119+
running = false
120+
break
121+
}
122+
}
123+
}
124+
}
125+
126+
func main() {
127+
sdl, err := NewSDL()
128+
if err != nil {
129+
log.Fatal(err)
130+
}
131+
defer sdl.Close()
132+
133+
// Open video
134+
input, err := ffmpeg.Open(os.Args[1])
135+
if err != nil {
136+
log.Fatal(err)
137+
}
138+
139+
// Decode frames in a goroutine
140+
var result error
141+
var wg sync.WaitGroup
142+
var w, h int32
143+
144+
// Decoder map function
145+
mapfn := func(stream int, par *ffmpeg.Par) (*ffmpeg.Par, error) {
146+
if stream == input.BestStream(media.VIDEO) {
147+
w = int32(par.Width())
148+
h = int32(par.Height())
149+
return par, nil
150+
}
151+
return nil, nil
152+
}
153+
154+
ch := make(chan *ffmpeg.Frame)
155+
156+
wg.Add(1)
157+
go func() {
158+
defer wg.Done()
159+
err := input.Decode(context.Background(), mapfn, func(stream int, frame *ffmpeg.Frame) error {
160+
ch <- frame
161+
return nil
162+
})
163+
if err != nil {
164+
result = errors.Join(result, err)
165+
}
166+
167+
// Close channel
168+
close(ch)
169+
170+
// Quit event
171+
sdl.PushQuitEvent()
172+
}()
173+
174+
// HACK
175+
time.Sleep(100 * time.Millisecond)
176+
if w == 0 || h == 0 {
177+
log.Fatal("No video stream found")
178+
}
179+
180+
title := filepath.Base(os.Args[1])
181+
meta := input.Metadata("title")
182+
if len(meta) > 0 {
183+
title = meta[0].Value()
184+
}
185+
186+
window, err := sdl.NewWindow(title, w, h)
187+
if err != nil {
188+
log.Fatal(err)
189+
}
190+
defer window.Close()
191+
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()
207+
208+
// Wait until all goroutines have finished
209+
wg.Wait()
210+
211+
// Return any errors
212+
if result != nil {
213+
log.Fatal(result)
214+
}
215+
}

sys/ffmpeg61/avutil_frame.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,10 @@ type jsonAVVideoFrame struct {
4040
type jsonAVFrame struct {
4141
*jsonAVAudioFrame
4242
*jsonAVVideoFrame
43-
NumPlanes int `json:"num_planes,omitempty"`
44-
PlaneBytes []int `json:"plane_bytes,omitempty"`
45-
Pts AVTimestamp `json:"pts,omitempty"`
46-
BestEffortTs AVTimestamp `json:"best_effort_timestamp,omitempty"`
47-
TimeBase AVRational `json:"time_base,omitempty"`
43+
NumPlanes int `json:"num_planes,omitempty"`
44+
PlaneBytes []int `json:"plane_bytes,omitempty"`
45+
Pts AVTimestamp `json:"pts,omitempty"`
46+
TimeBase AVRational `json:"time_base,omitempty"`
4847
}
4948

5049
func (ctx *AVFrame) MarshalJSON() ([]byte, error) {
@@ -58,11 +57,10 @@ func (ctx *AVFrame) MarshalJSON() ([]byte, error) {
5857
ChannelLayout: AVChannelLayout(ctx.ch_layout),
5958
BytesPerSample: AVUtil_get_bytes_per_sample(AVSampleFormat(ctx.format)),
6059
},
61-
Pts: AVTimestamp(ctx.pts),
62-
BestEffortTs: AVTimestamp(ctx.best_effort_timestamp),
63-
TimeBase: AVRational(ctx.time_base),
64-
NumPlanes: AVUtil_frame_get_num_planes(ctx),
65-
PlaneBytes: ctx.planesizes(),
60+
Pts: AVTimestamp(ctx.pts),
61+
TimeBase: AVRational(ctx.time_base),
62+
NumPlanes: AVUtil_frame_get_num_planes(ctx),
63+
PlaneBytes: ctx.planesizes(),
6664
})
6765
} else if ctx.width != 0 && ctx.height != 0 && ctx.PixFmt() != AV_PIX_FMT_NONE {
6866
// Video

0 commit comments

Comments
 (0)