Skip to content

Commit 7d067c9

Browse files
committed
Updated
1 parent e2bae22 commit 7d067c9

File tree

10 files changed

+376
-99
lines changed

10 files changed

+376
-99
lines changed
File renamed without changes.
File renamed without changes.

decoder.go

Lines changed: 165 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ type demuxer struct {
2323

2424
// decoder context - decodes packets into frames
2525
type decoder struct {
26-
stream int
27-
codec *ff.AVCodecContext
28-
frame *ff.AVFrame // Destination frame
26+
stream int
27+
codec *ff.AVCodecContext
28+
frame *ff.AVFrame // Destination frame
29+
reframe *ff.AVFrame // Destination frame after resample or resize
30+
resampler *ff.SWRContext // Resampler for audio
31+
rescaler *ff.SWSContext // Rescaler for video
2932
}
3033

3134
var _ Decoder = (*demuxer)(nil)
@@ -53,16 +56,13 @@ func newDemuxer(input *ff.AVFormatContext, mapfn DecoderMapFunc) (*demuxer, erro
5356
// destination frame. If it's nil then it's mostly a copy.
5457
var result error
5558
for _, stream := range streams {
56-
// Get decoder parameters
59+
// Get decoder parameters and map to a decoder
5760
parameters, err := mapfn(newStream(stream))
5861
if err != nil {
5962
result = errors.Join(result, err)
6063
} else if parameters == nil {
6164
continue
62-
}
63-
64-
// Create the decoder with the parameters
65-
if decoder, err := demuxer.newDecoder(stream, parameters); err != nil {
65+
} else if decoder, err := demuxer.newDecoder(stream, parameters); err != nil {
6666
result = errors.Join(result, err)
6767
} else {
6868
streamNum := stream.Index()
@@ -86,11 +86,29 @@ func newDemuxer(input *ff.AVFormatContext, mapfn DecoderMapFunc) (*demuxer, erro
8686
return demuxer, nil
8787
}
8888

89-
func (d *demuxer) newDecoder(stream *ff.AVStream, parameters Parameters) (*decoder, error) {
89+
func (d *demuxer) newDecoder(stream *ff.AVStream, dest Parameters) (*decoder, error) {
9090
decoder := new(decoder)
9191
decoder.stream = stream.Id()
9292

93-
// TODO: Use parameters to create the decoder
93+
// Use parameters to create the decoder resampler or resizer
94+
src := stream.CodecPar()
95+
if equals, err := equalsStream(dest, src); err != nil {
96+
return nil, err
97+
} else if !equals {
98+
switch src.CodecType() {
99+
case ff.AVMEDIA_TYPE_AUDIO:
100+
fmt.Println("TODO: set up resampler", dest)
101+
case ff.AVMEDIA_TYPE_VIDEO:
102+
if rescaler, frame, err := newResizer(dest, src); err != nil {
103+
return nil, err
104+
} else {
105+
decoder.rescaler = rescaler
106+
decoder.reframe = frame
107+
}
108+
default:
109+
return nil, fmt.Errorf("new decoder: unsupported stream type %v", src.CodecType())
110+
}
111+
}
94112

95113
// Create a codec context for the decoder
96114
codec := ff.AVCodec_find_decoder(stream.CodecPar().CodecID())
@@ -123,6 +141,24 @@ func (d *demuxer) newDecoder(stream *ff.AVStream, parameters Parameters) (*decod
123141
return decoder, nil
124142
}
125143

144+
func newResizer(dest Parameters, src *ff.AVCodecParameters) (*ff.SWSContext, *ff.AVFrame, error) {
145+
// Get native pixel format
146+
dest_pixel_format := ff.AVUtil_get_pix_fmt(dest.PixelFormat())
147+
148+
// Create scaling context and destination frame
149+
if ctx := ff.SWScale_get_context(
150+
src.Width(), src.Height(), src.PixelFormat(), // source
151+
dest.Width(), dest.Height(), dest_pixel_format, // destination
152+
ff.SWS_BILINEAR, nil, nil, nil); ctx == nil {
153+
return nil, nil, errors.New("failed to allocate swscale context")
154+
} else if frame := ff.AVUtil_frame_alloc(); frame == nil {
155+
ff.SWScale_free_context(ctx)
156+
return nil, nil, errors.New("failed to allocate frame")
157+
} else {
158+
return ctx, frame, nil
159+
}
160+
}
161+
126162
func (d *demuxer) close() error {
127163
var result error
128164

@@ -149,6 +185,21 @@ func (d *decoder) close() error {
149185
ff.AVCodec_free_context(d.codec)
150186
}
151187

188+
// Free the resampler
189+
if d.resampler != nil {
190+
ff.SWResample_free(d.resampler)
191+
}
192+
193+
// Free the rescaler
194+
if d.rescaler != nil {
195+
ff.SWScale_free_context(d.rescaler)
196+
}
197+
198+
// Free rescaler frame
199+
if d.reframe != nil {
200+
ff.AVUtil_frame_free(d.reframe)
201+
}
202+
152203
// Free destination frame
153204
if d.frame != nil {
154205
ff.AVUtil_frame_free(d.frame)
@@ -235,6 +286,7 @@ func (d *decoder) decode(packet *ff.AVPacket, demuxfn DecoderFunc, framefn Frame
235286
}
236287

237288
// get all the available frames from the decoder
289+
var result error
238290
for {
239291
if err := ff.AVCodec_receive_frame(d.codec, d.frame); errors.Is(err, syscall.EAGAIN) || errors.Is(err, io.EOF) {
240292
// Finished decoding packet or EOF
@@ -243,39 +295,121 @@ func (d *decoder) decode(packet *ff.AVPacket, demuxfn DecoderFunc, framefn Frame
243295
return err
244296
}
245297

246-
// Pass the frame
247-
if err := framefn(newFrame(d.frame)); errors.Is(err, io.EOF) {
248-
// End early
298+
// Resample or resize the frame, then pass to the frame function
299+
if frame, err := d.re(d.frame); err != nil {
300+
return err
301+
} else if err := framefn(newFrame(frame)); errors.Is(err, io.EOF) {
302+
// End early, return EOF
303+
result = io.EOF
249304
break
250305
} else if err != nil {
251306
return err
252307
}
253-
254-
// Resample or resize the frame, then pass back
255-
/*
256-
if frame, err := codec.(*decoder).re(r.frame); err != nil {
257-
return err
258-
} else if err := fn(frame); errors.Is(err, io.EOF) {
259-
// End early
260-
break
261-
} else if err != nil {
262-
return err
263-
}
264-
*/
265308
}
266309

267-
// TODO: Flush
268-
/*
269-
if frame, err := codec.(*decoder).re(nil); err != nil {
310+
// Flush the resizer or resampler if we haven't received an EOF
311+
if result == nil {
312+
if frame, err := d.re(nil); err != nil {
270313
return err
271314
} else if frame == nil {
272315
// NOOP
273-
} else if err := fn(frame); errors.Is(err, io.EOF) {
316+
} else if err := framefn(newFrame(d.frame)); errors.Is(err, io.EOF) {
274317
// NOOP
275318
} else if err != nil {
276319
return err
277-
}*/
320+
}
321+
}
278322

279-
// Return success
323+
// Return success or EOF
324+
return result
325+
}
326+
327+
func (d *decoder) re(src *ff.AVFrame) (*ff.AVFrame, error) {
328+
switch d.codec.Codec().Type() {
329+
case ff.AVMEDIA_TYPE_AUDIO:
330+
fmt.Println("TODO: resample audio", src)
331+
case ff.AVMEDIA_TYPE_VIDEO:
332+
if d.rescaler != nil && src != nil {
333+
// Rescale the video
334+
if err := rescale(d.rescaler, d.reframe, src); err != nil {
335+
return nil, err
336+
} else {
337+
return d.reframe, nil
338+
}
339+
}
340+
}
341+
342+
// if err := decoder.rescale(decoder.frame, src); err != nil {
343+
// return nil, err
344+
// }
345+
346+
// NO-OP - just return the source frame
347+
return src, nil
348+
}
349+
350+
func rescale(ctx *ff.SWSContext, dest, src *ff.AVFrame) error {
351+
// Copy properties from source
352+
if err := ff.AVUtil_frame_copy_props(dest, src); err != nil {
353+
return fmt.Errorf("failed to copy props: %w", err)
354+
}
355+
// Perform rescale
356+
if err := ff.SWScale_scale_frame(ctx, dest, src, false); err != nil {
357+
return fmt.Errorf("SWScale_scale_frame: %w", err)
358+
}
280359
return nil
281360
}
361+
362+
// Return an error if the parameters don't match the stream type (AUDIO, VIDEO)
363+
// Return true if the codec parameters are compatible with the stream
364+
func equalsStream(dest Parameters, src *ff.AVCodecParameters) (bool, error) {
365+
switch src.CodecType() {
366+
case ff.AVMEDIA_TYPE_AUDIO:
367+
if !dest.Type().Is(AUDIO) {
368+
return false, fmt.Errorf("source is audio, but destination is %v", dest.Type())
369+
} else {
370+
return equalsAudioPar(dest, src), nil
371+
}
372+
case ff.AVMEDIA_TYPE_VIDEO:
373+
if !dest.Type().Is(VIDEO) {
374+
return false, fmt.Errorf("source is video, but destination are %v", dest.Type())
375+
} else {
376+
return equalsVideoPar(dest, src), nil
377+
}
378+
default:
379+
return false, fmt.Errorf("unsupported source %v", src.CodecType())
380+
}
381+
}
382+
383+
// Return true if the audio parameters are compatible with the stream
384+
func equalsAudioPar(parameters Parameters, codec *ff.AVCodecParameters) bool {
385+
samplefmt := ff.AVUtil_get_sample_fmt_name(codec.SampleFormat())
386+
if samplefmt != parameters.SampleFormat() {
387+
return false
388+
}
389+
ch_layout := ff.AVChannelLayout(codec.ChannelLayout())
390+
channellayout, err := ff.AVUtil_channel_layout_describe(&ch_layout)
391+
if err != nil || channellayout != parameters.ChannelLayout() {
392+
return false
393+
}
394+
if codec.Samplerate() != parameters.Samplerate() {
395+
return false
396+
}
397+
// Matches
398+
return true
399+
}
400+
401+
// Return true if the video parameters are compatible with the stream
402+
func equalsVideoPar(parameters Parameters, codec *ff.AVCodecParameters) bool {
403+
pixelfmt := ff.AVUtil_get_pix_fmt_name(codec.PixelFormat())
404+
if pixelfmt != parameters.PixelFormat() {
405+
return false
406+
}
407+
if codec.Width() != parameters.Width() {
408+
return false
409+
}
410+
if codec.Height() != parameters.Height() {
411+
return false
412+
}
413+
// Matches
414+
return true
415+
}

decoder_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"fmt"
77
"image/jpeg"
8+
"io"
89
"os"
910
"path/filepath"
1011
"testing"
@@ -17,6 +18,7 @@ import (
1718
)
1819

1920
func Test_decoder_001(t *testing.T) {
21+
// Decode packets
2022
assert := assert.New(t)
2123

2224
manager := NewManager()
@@ -46,6 +48,7 @@ func Test_decoder_001(t *testing.T) {
4648
}
4749

4850
func Test_decoder_002(t *testing.T) {
51+
// Decode video frames
4952
assert := assert.New(t)
5053

5154
manager := NewManager()
@@ -93,3 +96,63 @@ func Test_decoder_002(t *testing.T) {
9396
// decode frames from the stream
9497
assert.NoError(decoder.Decode(context.Background(), framefn))
9598
}
99+
100+
func Test_decoder_003(t *testing.T) {
101+
// Decode video frames and resize them
102+
assert := assert.New(t)
103+
104+
manager := NewManager()
105+
media, err := manager.Open("./etc/test/sample.mp4", nil)
106+
if !assert.NoError(err) {
107+
t.SkipNow()
108+
}
109+
defer media.Close()
110+
111+
decoder, err := media.Decoder(func(stream Stream) (Parameters, error) {
112+
// Make greyscale images
113+
if stream.Type() == VIDEO {
114+
return manager.VideoParameters(640, 480, "yuv420p")
115+
}
116+
// Ignore other streams
117+
return nil, nil
118+
})
119+
if !assert.NoError(err) {
120+
t.SkipNow()
121+
}
122+
123+
// Frame function
124+
n := 0
125+
// tmpdir := t.TempDir()
126+
tmpdir, err := os.MkdirTemp("", "media_test")
127+
if !assert.NoError(err) {
128+
t.SkipNow()
129+
}
130+
framefn := func(frame Frame) error {
131+
if frame.Type() != VIDEO {
132+
return nil
133+
}
134+
filename := filepath.Join(tmpdir, fmt.Sprintf("frame%03d.jpg", n))
135+
w, err := os.Create(filename)
136+
if err != nil {
137+
return err
138+
}
139+
defer w.Close()
140+
if image, err := frame.Image(); err != nil {
141+
return err
142+
} else if err := jpeg.Encode(w, image, nil); err != nil {
143+
return err
144+
} else {
145+
t.Logf("Frame %d: %dx%d (%q) => %s", n, frame.Width(), frame.Height(), frame.PixelFormat(), filename)
146+
n++
147+
}
148+
// Stop after 10 frames
149+
if n >= 10 {
150+
return io.EOF
151+
} else {
152+
return nil
153+
}
154+
}
155+
156+
// decode frames from the stream
157+
assert.NoError(decoder.Decode(context.Background(), framefn))
158+
}

interfaces.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ type Manager interface {
6767

6868
// Return audio parameters for encoding
6969
// ChannelLayout, SampleFormat, Samplerate
70-
AudioParameters(string, string, int) (AudioParameters, error)
70+
AudioParameters(string, string, int) (Parameters, error)
7171

7272
// Return video parameters for encoding
7373
// Width, Height, PixelFormat
74-
VideoParameters(int, int, string) (VideoParameters, error)
74+
VideoParameters(int, int, string) (Parameters, error)
7575

7676
// Return codec parameters for audio encoding
7777
// Codec name and AudioParameters

manager.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,13 @@ func (manager *manager) Codecs() []Metadata {
247247

248248
// Return audio parameters for encoding
249249
// ChannelLayout, SampleFormat, Samplerate
250-
func (manager *manager) AudioParameters(channels string, samplefmt string, samplerate int) (AudioParameters, error) {
250+
func (manager *manager) AudioParameters(channels string, samplefmt string, samplerate int) (Parameters, error) {
251251
return newAudioParametersEx(channels, samplefmt, samplerate)
252252
}
253253

254254
// Return video parameters for encoding
255255
// Width, Height, PixelFormat, Framerate
256-
func (manager *manager) VideoParameters(width int, height int, pixelfmt string) (VideoParameters, error) {
256+
func (manager *manager) VideoParameters(width int, height int, pixelfmt string) (Parameters, error) {
257257
return newVideoParametersEx(width, height, pixelfmt)
258258
}
259259

0 commit comments

Comments
 (0)