Skip to content

Commit fe06c11

Browse files
committed
Updates for sampling
1 parent 2ff6a2c commit fe06c11

File tree

7 files changed

+212
-48
lines changed

7 files changed

+212
-48
lines changed

etc/test/sample_resized.png

167 KB
Loading

pkg/ffmpeg/decoder.go

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ type Decoder struct {
2121
frame *ff.AVFrame // Destination frame
2222
}
2323

24-
// DecoderFrameFn is a function which is called to send a frame after decoding. It should
25-
// return nil to continue decoding or io.EOF to stop.
26-
type DecoderFrameFn func(int, *Frame) error
27-
2824
////////////////////////////////////////////////////////////////////////////////
2925
// LIFECYCLE
3026

@@ -119,15 +115,7 @@ func (d *Decoder) decode(packet *ff.AVPacket, fn DecoderFrameFn) error {
119115
return err
120116
}
121117

122-
// Resample or resize the frame, then pass to the frame function
123-
//frame, err := d.re(d.frame)
124-
//if err != nil {
125-
// return err
126-
//}
127-
128-
// Copy over the timebase and ptr from the stream
129-
d.frame.SetTimeBase(d.timeBase)
130-
d.frame.SetPts(d.frame.Pts())
118+
// TODO: Modify Pts?
131119

132120
// Pass back to the caller
133121
if err := fn(d.stream, (*Frame)(d.frame)); errors.Is(err, io.EOF) {
@@ -139,33 +127,12 @@ func (d *Decoder) decode(packet *ff.AVPacket, fn DecoderFrameFn) error {
139127

140128
// Re-allocate frames for next iteration
141129
ff.AVUtil_frame_unref(d.frame)
142-
// ff.AVUtil_frame_unref(d.reframe)
143130
}
144131

145-
// Flush the resizer or resampler if we haven't received an EOF
146-
/*
147-
if result == nil {
148-
finished := false
149-
for {
150-
if finished {
151-
break
152-
}
153-
if frame, err := d.reflush(d.frame); err != nil {
154-
return err
155-
} else if frame == nil {
156-
finished = true
157-
} else if err := framefn(newFrame(frame)); errors.Is(err, io.EOF) {
158-
finished = true
159-
} else if err != nil {
160-
return err
161-
}
162-
163-
// Re-allocate frames for next iteration
164-
ff.AVUtil_frame_unref(d.frame)
165-
ff.AVUtil_frame_unref(d.reframe)
166-
}
167-
}
168-
*/
132+
// Flush
133+
if err := fn(d.stream, nil); err != nil && !errors.Is(err, io.EOF) {
134+
result = errors.Join(result, err)
135+
}
169136

170137
// Return success or EOF
171138
return result

pkg/ffmpeg/re.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package ffmpeg
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
// Packages
7+
)
8+
9+
////////////////////////////////////////////////////////////////////////////////
10+
// TYPES
11+
12+
// Re implements a resampler and rescaler for audio and video frames.
13+
// May need to extend it for subtitles later on
14+
type Re struct {
15+
t Type
16+
audio *resampler
17+
video *rescaler
18+
}
19+
20+
////////////////////////////////////////////////////////////////////////////////
21+
// LIFECYCLE
22+
23+
func NewRe(par *Par, force bool) (*Re, error) {
24+
re := new(Re)
25+
re.t = par.Type()
26+
switch re.t {
27+
case AUDIO:
28+
if audio, err := NewResampler(par, force); err != nil {
29+
return nil, err
30+
} else {
31+
re.audio = audio
32+
}
33+
case VIDEO:
34+
if video, err := NewRescaler(par, force); err != nil {
35+
return nil, err
36+
} else {
37+
re.video = video
38+
}
39+
default:
40+
return nil, fmt.Errorf("invalid resampling/rescaling type: %v", par.Type())
41+
}
42+
43+
// Return success
44+
return re, nil
45+
}
46+
47+
func (re *Re) Close() error {
48+
var result error
49+
if re.audio != nil {
50+
result = errors.Join(result, re.audio.Close())
51+
}
52+
if re.video != nil {
53+
result = errors.Join(result, re.video.Close())
54+
}
55+
re.audio = nil
56+
re.video = nil
57+
// Return any errors
58+
return result
59+
}
60+
61+
////////////////////////////////////////////////////////////////////////////////
62+
// PUBLIC METHODS
63+
64+
func (re *Re) Frame(src *Frame) (*Frame, error) {
65+
// Check type - if not flush
66+
if src != nil {
67+
if src.Type() != re.t {
68+
return nil, fmt.Errorf("frame type mismatch: %v", src.Type())
69+
}
70+
}
71+
switch re.t {
72+
case AUDIO:
73+
return re.audio.Frame(src)
74+
case VIDEO:
75+
return re.video.Frame(src)
76+
default:
77+
return src, nil
78+
}
79+
}

pkg/ffmpeg/re_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package ffmpeg_test
2+
3+
import (
4+
"context"
5+
"image/png"
6+
"os"
7+
"testing"
8+
9+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
10+
assert "github.com/stretchr/testify/assert"
11+
)
12+
13+
func Test_re_001(t *testing.T) {
14+
assert := assert.New(t)
15+
16+
r, err := os.Open("../../etc/test/sample.png")
17+
if !assert.NoError(err) {
18+
t.FailNow()
19+
}
20+
defer r.Close()
21+
img, err := png.Decode(r)
22+
if !assert.NoError(err) {
23+
t.FailNow()
24+
}
25+
frame, err := ffmpeg.FrameFromImage(img)
26+
if !assert.NoError(err) {
27+
t.FailNow()
28+
}
29+
defer frame.Close()
30+
31+
// Create a resizer
32+
re, err := ffmpeg.NewRe(ffmpeg.VideoPar("gray8", "vga", 25), false)
33+
if !assert.NoError(err) {
34+
t.FailNow()
35+
}
36+
defer re.Close()
37+
38+
// Resize the frame
39+
dest, err := re.Frame(frame)
40+
if !assert.NoError(err) {
41+
t.FailNow()
42+
}
43+
44+
// Get image from frame
45+
destimg, err := dest.ImageFromFrame()
46+
if !assert.NoError(err) {
47+
t.FailNow()
48+
}
49+
50+
// Save image
51+
w, err := os.CreateTemp("", "*_resized.png")
52+
if !assert.NoError(err) {
53+
t.FailNow()
54+
}
55+
defer w.Close()
56+
57+
err = png.Encode(w, destimg)
58+
if !assert.NoError(err) {
59+
t.FailNow()
60+
}
61+
62+
t.Log(r.Name(), "=>", w.Name())
63+
}
64+
65+
func Test_re_002(t *testing.T) {
66+
assert := assert.New(t)
67+
68+
r, err := ffmpeg.Open("../../etc/test/sample.mp3")
69+
//r, err := ffmpeg.Open("/Volumes/Drobo/Media/Music/ABBA/Gold_ Greatest Hits/01 Dancing Queen.m4a")
70+
if !assert.NoError(err) {
71+
t.FailNow()
72+
}
73+
defer r.Close()
74+
75+
// Make resampler
76+
re, err := ffmpeg.NewRe(ffmpeg.AudioPar("s16", "stereo", 44100), true)
77+
if !assert.NoError(err) {
78+
t.FailNow()
79+
}
80+
defer re.Close()
81+
82+
// Write out resampled audio
83+
w, err := os.CreateTemp("", "*_resampled.sw")
84+
if !assert.NoError(err) {
85+
t.FailNow()
86+
}
87+
defer w.Close()
88+
89+
// Decode function
90+
decodefn := func(_ int, frame *ffmpeg.Frame) error {
91+
if frame != nil && frame.Type() != ffmpeg.AUDIO {
92+
return nil
93+
}
94+
resampled, err := re.Frame(frame)
95+
if err != nil {
96+
return err
97+
}
98+
if resampled != nil {
99+
if _, err := w.Write(resampled.Bytes(0)); err != nil {
100+
return err
101+
}
102+
}
103+
return nil
104+
}
105+
// Get audio frames
106+
if err := r.Decode(context.Background(), decodefn, nil); !assert.NoError(err) {
107+
t.FailNow()
108+
}
109+
// Print
110+
t.Log(" play with: ffplay -f s16le -ar 44100 -ac 2", w.Name())
111+
112+
}

pkg/ffmpeg/reader.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ type reader_callback struct {
3535
// stream parameters if you want to copy the stream without any changes.
3636
type DecoderMapFunc func(int, *Par) (*Par, error)
3737

38+
// DecoderFrameFn is a function which is called to send a frame after decoding. It should
39+
// return nil to continue decoding or io.EOF to stop.
40+
type DecoderFrameFn func(int, *Frame) error
41+
3842
////////////////////////////////////////////////////////////////////////////////
3943
// LIFECYCLE
4044

pkg/ffmpeg/resampler.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,23 +90,23 @@ func (r *resampler) Frame(src *Frame) (*Frame, error) {
9090
}
9191
}
9292

93-
// Copy parameters from the source frame
94-
// TODO: this seems to mess with the sample rate
95-
//if src != nil {
96-
// if err := r.dest.CopyPropsFromFrame(src); err != nil {
97-
// return nil, err
98-
// }
99-
//}
100-
10193
// Perform resampling
94+
fmt.Println("Do convert")
10295
if err := ff.SWResample_convert_frame(r.ctx, (*ff.AVFrame)(src), (*ff.AVFrame)(r.dest)); err != nil {
10396
return nil, fmt.Errorf("SWResample_convert_frame: %w", err)
10497
}
10598

10699
// Get remaining samples
107-
if src == nil {
100+
for {
108101
samples := ff.SWResample_get_delay(r.ctx, int64(r.dest.SampleRate()))
102+
if samples <= 0 {
103+
break
104+
}
109105
fmt.Println("TODO: SWResample_get_delay remaining samples=", samples)
106+
if err := ff.SWResample_convert_frame(r.ctx, nil, (*ff.AVFrame)(r.dest)); err != nil {
107+
return nil, fmt.Errorf("SWResample_convert_frame: %w", err)
108+
}
109+
fmt.Println(r.dest)
110110
}
111111

112112
// Return the destination frame or nil
@@ -136,6 +136,9 @@ func newResampler(dest, src *Frame) (*ff.SWRContext, error) {
136136
return nil, fmt.Errorf("SWResample_set_opts: %w", err)
137137
}
138138

139+
fmt.Println("new context")
140+
fmt.Println(" src", src)
141+
139142
// Initialize the resampling context
140143
if err := ff.SWResample_init(ctx); err != nil {
141144
ff.SWResample_free(ctx)

pkg/ffmpeg/rescaler.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"errors"
55

66
// Packages
7-
87
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
98
)
109

0 commit comments

Comments
 (0)