Skip to content

Commit d14921d

Browse files
committed
Updates
1 parent 36b24b6 commit d14921d

File tree

13 files changed

+1063
-340
lines changed

13 files changed

+1063
-340
lines changed

cmd/examples/sdlplayer/context.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
)
8+
9+
///////////////////////////////////////////////////////////////////////////////
10+
// PUBLIC METHODS
11+
12+
// ContextForSignal returns a context object which is cancelled when a signal
13+
// is received. It returns nil if no signal parameter is provided
14+
func ContextForSignal(signals ...os.Signal) context.Context {
15+
if len(signals) == 0 {
16+
return nil
17+
}
18+
19+
ch := make(chan os.Signal, 1)
20+
ctx, cancel := context.WithCancel(context.Background())
21+
22+
// Send message on channel when signal received
23+
signal.Notify(ch, signals...)
24+
25+
// When any signal received, call cancel
26+
go func() {
27+
<-ch
28+
cancel()
29+
}()
30+
31+
// Return success
32+
return ctx
33+
}

cmd/examples/sdlplayer/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* This example demonstrates how to play audio and video files using SDL2. */
2+
package main
3+
4+
import (
5+
"errors"
6+
"fmt"
7+
"os"
8+
"syscall"
9+
)
10+
11+
func main() {
12+
// Bail out when we receive a signal
13+
ctx := ContextForSignal(os.Interrupt, syscall.SIGQUIT)
14+
15+
// Create a player object
16+
player := NewPlayer()
17+
defer player.Close()
18+
19+
// Open the file
20+
var result error
21+
if len(os.Args) == 2 {
22+
result = player.OpenUrl(os.Args[1])
23+
} else {
24+
result = errors.New("usage: sdlplayer <filename>")
25+
}
26+
if result != nil {
27+
fmt.Fprintln(os.Stderr, result)
28+
os.Exit(-1)
29+
}
30+
31+
// Play
32+
if err := player.Play(ctx); err != nil {
33+
fmt.Fprintln(os.Stderr, err)
34+
os.Exit(-1)
35+
}
36+
}

cmd/examples/sdlplayer/player.go

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package main
2+
3+
import (
4+
5+
// Packages
6+
"context"
7+
"errors"
8+
"fmt"
9+
"os"
10+
"sync"
11+
"unsafe"
12+
13+
"github.com/mutablelogic/go-media/pkg/ffmpeg"
14+
"github.com/mutablelogic/go-media/pkg/sdl"
15+
16+
// Namespace imports
17+
. "github.com/mutablelogic/go-media"
18+
)
19+
20+
type Player struct {
21+
input *ffmpeg.Reader
22+
ctx *ffmpeg.Context
23+
audio *ffmpeg.Par
24+
video *ffmpeg.Par
25+
videoevent uint32
26+
audioevent uint32
27+
}
28+
29+
func NewPlayer() *Player {
30+
return &Player{}
31+
}
32+
33+
func (p *Player) Close() error {
34+
var result error
35+
36+
// Close resources
37+
if p.ctx != nil {
38+
result = errors.Join(result, p.ctx.Close())
39+
}
40+
if p.input != nil {
41+
result = errors.Join(result, p.input.Close())
42+
}
43+
44+
// Return any errors
45+
return result
46+
}
47+
48+
func (p *Player) OpenUrl(url string) error {
49+
input, err := ffmpeg.Open(url)
50+
if err != nil {
51+
return err
52+
}
53+
p.input = input
54+
55+
// Map input streams - find best audio and video streams
56+
ctx, err := p.input.Map(func(stream int, par *ffmpeg.Par) (*ffmpeg.Par, error) {
57+
if stream == p.input.BestStream(VIDEO) {
58+
p.video = par
59+
return par, nil
60+
} else if stream == p.input.BestStream(AUDIO) {
61+
p.audio = par
62+
return par, nil
63+
} else {
64+
return nil, nil
65+
}
66+
})
67+
if err != nil {
68+
return err
69+
} else {
70+
p.ctx = ctx
71+
p.input = input
72+
}
73+
return nil
74+
}
75+
76+
func (p *Player) Type() Type {
77+
t := NONE
78+
if p.video != nil {
79+
t |= VIDEO
80+
}
81+
if p.audio != nil {
82+
t |= AUDIO
83+
}
84+
return t
85+
}
86+
87+
// Return media title
88+
func (p *Player) Title() string {
89+
title := p.input.Metadata("title")
90+
if len(title) > 0 {
91+
return title[0].Value()
92+
}
93+
return fmt.Sprint(p.Type())
94+
}
95+
96+
func (p *Player) Play(ctx context.Context) error {
97+
var window *sdl.Window
98+
var audio *sdl.Audio
99+
100+
// Create a new SDL context
101+
sdl, err := sdl.New(p.Type())
102+
if err != nil {
103+
return err
104+
}
105+
defer sdl.Close()
106+
107+
// Create a window for video
108+
if p.video != nil {
109+
if w, err := sdl.NewVideo(p.Title(), p.video); err != nil {
110+
return err
111+
} else {
112+
window = w
113+
}
114+
defer window.Close()
115+
116+
// Register a method to push video rendering
117+
p.videoevent = sdl.Register(func(frame unsafe.Pointer) {
118+
var result error
119+
frame_ := (*ffmpeg.Frame)(frame)
120+
if err := window.RenderFrame(frame_); err != nil {
121+
result = errors.Join(result, err)
122+
}
123+
if err := window.Flush(); err != nil {
124+
result = errors.Join(result, err)
125+
}
126+
if err := frame_.Close(); err != nil {
127+
result = errors.Join(result, err)
128+
}
129+
if result != nil {
130+
fmt.Fprintln(os.Stderr, result)
131+
}
132+
/*
133+
// Pause to present the frame at the correct PTS
134+
if pts != ffmpeg.TS_UNDEFINED && pts < frame.Ts() {
135+
pause := frame.Ts() - pts
136+
if pause > 0 {
137+
sdl.Delay(uint32(pause * 1000))
138+
}
139+
}
140+
141+
// Set current timestamp
142+
pts = frame.Ts()
143+
144+
// Render the frame, release the frame resources
145+
if err := w.RenderFrame(frame); err != nil {
146+
log.Print(err)
147+
} else if err := w.Flush(); err != nil {
148+
log.Print(err)
149+
} else if err := frame.Close(); err != nil {
150+
log.Print(err)
151+
}
152+
*/
153+
})
154+
}
155+
156+
// Create audio
157+
if p.audio != nil {
158+
if a, err := sdl.NewAudio(p.audio); err != nil {
159+
return err
160+
} else {
161+
audio = a
162+
}
163+
defer audio.Close()
164+
165+
// Register a method to push audio rendering
166+
p.audioevent = sdl.Register(func(frame unsafe.Pointer) {
167+
//frame_ := (*ffmpeg.Frame)(frame)
168+
//fmt.Println("TODO: Audio", frame_)
169+
})
170+
}
171+
172+
// Start go routine to decode the audio and video frames
173+
var wg sync.WaitGroup
174+
wg.Add(1)
175+
go func() {
176+
defer wg.Done()
177+
if err := p.decode(ctx, sdl); err != nil {
178+
fmt.Fprintln(os.Stderr, err)
179+
}
180+
}()
181+
182+
// Run loop with events for audio and video
183+
var result error
184+
if err := sdl.Run(ctx); err != nil {
185+
result = err
186+
}
187+
188+
// Wait for go routines to finish
189+
wg.Wait()
190+
191+
// Return any errors
192+
return result
193+
}
194+
195+
// Goroutine decoder
196+
func (p *Player) decode(ctx context.Context, sdl *sdl.Context) error {
197+
return p.input.DecodeWithContext(ctx, p.ctx, func(stream int, frame *ffmpeg.Frame) error {
198+
if frame.Type().Is(VIDEO) {
199+
if copy, err := frame.Copy(); err != nil {
200+
fmt.Println("Unable to make a frame copy: ", err)
201+
} else {
202+
// TODO: Make a copy of the frame
203+
sdl.Post(p.videoevent, unsafe.Pointer(copy))
204+
}
205+
}
206+
if frame.Type().Is(AUDIO) {
207+
// TODO: Make a copy of the frame
208+
sdl.Post(p.audioevent, unsafe.Pointer(frame))
209+
}
210+
return nil
211+
})
212+
}

cmd/examples/transcode/main.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package main
22

33
import (
4+
"errors"
5+
"fmt"
46
"log"
57
"os"
8+
"sync"
9+
"syscall"
610

711
// Packages
812
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
913
)
1014

1115
// This example encodes an audio an video stream to a file
1216
func main() {
17+
// Bail out when we receive a signal
18+
ctx := ContextForSignal(os.Interrupt, syscall.SIGQUIT)
19+
1320
// Check we have a filename
1421
if len(os.Args) != 3 {
1522
log.Fatal("Usage: transcode <in> <out>")
@@ -21,4 +28,70 @@ func main() {
2128
log.Fatal(err)
2229
}
2330
defer in.Close()
31+
32+
// Map to output file
33+
decoding, err := in.Map(func(stream int, par *ffmpeg.Par) (*ffmpeg.Par, error) {
34+
// This is where you specify the output format for the input stream
35+
return par, nil
36+
})
37+
if err != nil {
38+
log.Fatal(err)
39+
}
40+
41+
// Create an output file from from the map
42+
out, err := ffmpeg.Create(os.Args[2], ffmpeg.OptContext(decoding))
43+
if err != nil {
44+
log.Fatal(err)
45+
}
46+
defer out.Close()
47+
48+
// Decoding goroutine
49+
var result error
50+
var wg sync.WaitGroup
51+
wg.Add(1)
52+
go func() {
53+
defer wg.Done()
54+
55+
// This is where we decode the stream
56+
result = errors.Join(result, in.DecodeWithContext(ctx, decoding, func(stream int, frame *ffmpeg.Frame) error {
57+
58+
// Add the frame onto the encoding queue
59+
fmt.Println("->DECODE:", stream, frame)
60+
decoding.C(stream) <- frame
61+
fmt.Println("<-DECODE")
62+
63+
// Return success
64+
return nil
65+
}))
66+
}()
67+
68+
// Encoding goroutine
69+
wg.Add(1)
70+
go func() {
71+
defer wg.Done()
72+
73+
// This is where we encode the stream
74+
result = errors.Join(result, out.Encode(ctx, func(stream int) (*ffmpeg.Frame, error) {
75+
fmt.Println("->ENCODE", stream)
76+
select {
77+
case frame := <-decoding.C(stream):
78+
fmt.Println("<-ENCODE", frame)
79+
return frame, nil
80+
default:
81+
// Not ready to pass a frame back
82+
fmt.Println("<-ENCODE", nil)
83+
return nil, nil
84+
}
85+
}, nil))
86+
if result != nil {
87+
fmt.Println("ERROR:", result)
88+
}
89+
}()
90+
91+
wg.Wait()
92+
93+
if result != nil {
94+
log.Fatal(result)
95+
}
96+
fmt.Println("Transcoded to", out)
2497
}

0 commit comments

Comments
 (0)