diff --git a/filter_graph_test.go b/filter_graph_test.go index 9ea02bf..ac451bf 100644 --- a/filter_graph_test.go +++ b/filter_graph_test.go @@ -1,6 +1,3 @@ -// TODO Fix https://github.com/asticode/go-astiav/actions/runs/5853322732/job/15867145888 -//go:build !windows - package astiav import ( @@ -12,88 +9,194 @@ import ( ) func TestFilterGraph(t *testing.T) { - fg := AllocFilterGraph() - defer fg.Free() - cl := fg.Class() + fg1 := AllocFilterGraph() + require.NotNil(t, fg1) + defer fg1.Free() + cl := fg1.Class() require.NotNil(t, cl) require.Equal(t, "AVFilterGraph", cl.Name()) - fg.SetThreadCount(2) - require.Equal(t, 2, fg.ThreadCount()) - fg.SetThreadType(ThreadTypeSlice) - require.Equal(t, ThreadTypeSlice, fg.ThreadType()) - - bufferSink := FindFilterByName("buffersink") - require.NotNil(t, bufferSink) - - fcOut, err := fg.NewFilterContext(bufferSink, "filter_out", nil) - require.NoError(t, err) - defer fcOut.Free() - cl = fcOut.Class() - require.NotNil(t, cl) - require.Equal(t, "AVFilter", cl.Name()) - - inputs := AllocFilterInOut() - defer inputs.Free() - inputs.SetName("out") - inputs.SetFilterContext(fcOut) - inputs.SetPadIdx(0) - inputs.SetNext(nil) - - var outputs *FilterInOut - defer func() { - if outputs != nil { - outputs.Free() - } - }() - var fcIns []*FilterContext - for i := 0; i < 2; i++ { - bufferSrc := FindFilterByName("buffer") - require.NotNil(t, bufferSrc) - - fcIn, err := fg.NewFilterContext(bufferSrc, fmt.Sprintf("filter_in_%d", i+1), FilterArgs{ - "pix_fmt": strconv.Itoa(int(PixelFormatYuv420P)), - "pixel_aspect": "1/1", - "time_base": "1/1000", - "video_size": "1x1", - }) + fg1.SetThreadCount(2) + require.Equal(t, 2, fg1.ThreadCount()) + fg1.SetThreadType(ThreadTypeSlice) + require.Equal(t, ThreadTypeSlice, fg1.ThreadType()) + + type command struct { + args string + cmd string + flags FilterCommandFlags + resp string + target string + withError bool + } + type link struct { + channelLayout ChannelLayout + frameRate Rational + height int + mediaType MediaType + pixelFormat PixelFormat + sampleAspectRatio Rational + sampleFormat SampleFormat + sampleRate int + timeBase Rational + width int + } + type graph struct { + buffersinkExpectedInput link + buffersinkName string + buffersrcName string + commands []command + content string + s string + sources []FilterArgs + } + for _, v := range []graph{ + { + buffersinkExpectedInput: link{ + frameRate: NewRational(4, 1), + height: 4, + mediaType: MediaTypeVideo, + pixelFormat: PixelFormatRgba, + sampleAspectRatio: NewRational(1, 4), + timeBase: NewRational(1, 4), + width: 2, + }, + buffersinkName: "buffersink", + buffersrcName: "buffer", + commands: []command{ + { + args: "a", + cmd: "invalid", + flags: NewFilterCommandFlags(), + target: "scale", + withError: true, + }, + { + args: "4", + cmd: "width", + flags: NewFilterCommandFlags().Add(FilterCommandFlagOne), + target: "scale", + }, + }, + content: "[input_1]scale=2x4,settb=1/4,fps=fps=4/1,format=pix_fmts=rgba,setsar=1/4", + s: " +--------------+\nParsed_setsar_4:default--[2x4 1:4 rgba]--default| filter_out |\n | (buffersink) |\n +--------------+\n\n+-------------+\n| filter_in_1 |default--[1x2 1:2 yuv420p]--Parsed_scale_0:default\n| (buffer) |\n+-------------+\n\n +----------------+\nfilter_in_1:default--[1x2 1:2 yuv420p]--default| Parsed_scale_0 |default--[2x4 1:2 rgba]--Parsed_settb_1:default\n | (scale) |\n +----------------+\n\n +----------------+\nParsed_scale_0:default--[2x4 1:2 rgba]--default| Parsed_settb_1 |default--[2x4 1:2 rgba]--Parsed_fps_2:default\n | (settb) |\n +----------------+\n\n +--------------+\nParsed_settb_1:default--[2x4 1:2 rgba]--default| Parsed_fps_2 |default--[2x4 1:2 rgba]--Parsed_format_3:default\n | (fps) |\n +--------------+\n\n +-----------------+\nParsed_fps_2:default--[2x4 1:2 rgba]--default| Parsed_format_3 |default--[2x4 1:2 rgba]--Parsed_setsar_4:default\n | (format) |\n +-----------------+\n\n +-----------------+\nParsed_format_3:default--[2x4 1:2 rgba]--default| Parsed_setsar_4 |default--[2x4 1:4 rgba]--filter_out:default\n | (setsar) |\n +-----------------+\n\n", + sources: []FilterArgs{ + { + "height": "2", + "pix_fmt": strconv.Itoa(int(PixelFormatYuv420P)), + "sar": "1/2", + "time_base": "1/2", + "width": "1", + }, + }, + }, + { + buffersinkExpectedInput: link{ + channelLayout: ChannelLayoutStereo, + mediaType: MediaTypeAudio, + sampleFormat: SampleFormatS16, + sampleRate: 4, + timeBase: NewRational(1, 4), + }, + buffersinkName: "abuffersink", + buffersrcName: "abuffer", + content: "[input_1]aformat=sample_fmts=s16:channel_layouts=stereo:sample_rates=4,asettb=1/4", + s: " +---------------+\nParsed_asettb_1:default--[4Hz s16:stereo]--default| filter_out |\n | (abuffersink) |\n +---------------+\n\n+-------------+\n| filter_in_1 |default--[2Hz fltp:mono]--auto_aresample_0:default\n| (abuffer) |\n+-------------+\n\n +------------------+\nauto_aresample_0:default--[4Hz s16:stereo]--default| Parsed_aformat_0 |default--[4Hz s16:stereo]--Parsed_asettb_1:default\n | (aformat) |\n +------------------+\n\n +-----------------+\nParsed_aformat_0:default--[4Hz s16:stereo]--default| Parsed_asettb_1 |default--[4Hz s16:stereo]--filter_out:default\n | (asettb) |\n +-----------------+\n\n +------------------+\nfilter_in_1:default--[2Hz fltp:mono]--default| auto_aresample_0 |default--[4Hz s16:stereo]--Parsed_aformat_0:default\n | (aresample) |\n +------------------+\n\n", + sources: []FilterArgs{ + { + "channel_layout": ChannelLayoutMono.String(), + "sample_fmt": strconv.Itoa(int(SampleFormatFltp)), + "sample_rate": "2", + "time_base": "1/2", + }, + }, + }, + } { + fg := AllocFilterGraph() + require.NotNil(t, fg) + defer fg.Free() + + buffersrc := FindFilterByName(v.buffersrcName) + require.NotNil(t, buffersrc) + buffersink := FindFilterByName(v.buffersinkName) + require.NotNil(t, buffersink) + + buffersinkContext, err := fg.NewFilterContext(buffersink, "filter_out", nil) require.NoError(t, err) - fcIns = append(fcIns, fcIn) - defer fcIn.Free() - - o := AllocFilterInOut() - o.SetName(fmt.Sprintf("input_%d", i+1)) - o.SetFilterContext(fcIn) - o.SetPadIdx(0) - o.SetNext(outputs) + cl = buffersinkContext.Class() + require.NotNil(t, cl) + require.Equal(t, "AVFilter", cl.Name()) + + inputs := AllocFilterInOut() + defer inputs.Free() + inputs.SetName("out") + inputs.SetFilterContext(buffersinkContext) + inputs.SetPadIdx(0) + inputs.SetNext(nil) + + var outputs *FilterInOut + defer func() { + if outputs != nil { + outputs.Free() + } + }() + + var buffersrcContexts []*FilterContext + for idx, src := range v.sources { + buffersrcContext, err := fg.NewFilterContext(buffersrc, fmt.Sprintf("filter_in_%d", idx+1), src) + require.NoError(t, err) + buffersrcContexts = append(buffersrcContexts, buffersrcContext) + + o := AllocFilterInOut() + o.SetName(fmt.Sprintf("input_%d", idx+1)) + o.SetFilterContext(buffersrcContext) + o.SetPadIdx(0) + o.SetNext(outputs) + + outputs = o + } - outputs = o - } + require.NoError(t, fg.Parse(v.content, inputs, outputs)) + require.NoError(t, fg.Configure()) + + require.Equal(t, 1, buffersinkContext.NbInputs()) + links := buffersinkContext.Inputs() + require.Equal(t, 1, len(links)) + e, g := v.buffersinkExpectedInput, links[0] + require.Equal(t, e.frameRate, g.FrameRate()) + require.Equal(t, e.mediaType, g.MediaType()) + require.Equal(t, e.timeBase, g.TimeBase()) + switch e.mediaType { + case MediaTypeAudio: + require.True(t, e.channelLayout.Equal(g.ChannelLayout())) + require.Equal(t, e.sampleFormat, g.SampleFormat()) + require.Equal(t, e.sampleRate, g.SampleRate()) + default: + require.Equal(t, e.height, g.Height()) + require.Equal(t, e.pixelFormat, g.PixelFormat()) + require.Equal(t, e.sampleAspectRatio, g.SampleAspectRatio()) + require.Equal(t, e.width, g.Width()) + } - err = fg.Parse("[input_1]scale=2x2[scaled_1];[input_2]scale=3x3[scaled_2];[scaled_1][scaled_2]overlay", inputs, outputs) - require.NoError(t, err) - - err = fg.Configure() - require.NoError(t, err) - - require.Equal(t, 1, fcOut.NbInputs()) - require.Equal(t, 1, len(fcOut.Inputs())) - require.Equal(t, NewRational(1, 1000), fcOut.Inputs()[0].TimeBase()) - require.Equal(t, 0, fcOut.NbOutputs()) - for _, fc := range fcIns { - require.Equal(t, 0, fc.NbInputs()) - require.Equal(t, 1, fc.NbOutputs()) - require.Equal(t, 1, len(fc.Outputs())) - require.Equal(t, NewRational(1, 1000), fc.Outputs()[0].TimeBase()) - } + for _, buffersrcContext := range buffersrcContexts { + require.Equal(t, 0, buffersrcContext.NbInputs()) + require.Equal(t, 1, buffersrcContext.NbOutputs()) + links := buffersrcContext.Outputs() + require.Equal(t, 1, len(links)) + require.Equal(t, v.buffersinkExpectedInput.mediaType, links[0].MediaType()) + } - resp, err := fg.SendCommand("scale", "invalid", "a", NewFilterCommandFlags()) - require.Error(t, err) - require.Empty(t, resp) - resp, err = fg.SendCommand("scale", "width", "4", NewFilterCommandFlags().Add(FilterCommandFlagOne)) - require.NoError(t, err) - require.Empty(t, resp) + require.Equal(t, v.s, fg.String()) - require.Equal(t, " +--------------+\nParsed_overlay_2:default--[2x2 1:1 yuv420p]--default| filter_out |\n | (buffersink) |\n +--------------+\n\n+-------------+\n| filter_in_1 |default--[1x1 1:1 yuv420p]--Parsed_scale_0:default\n| (buffer) |\n+-------------+\n\n+-------------+\n| filter_in_2 |default--[1x1 1:1 yuv420p]--Parsed_scale_1:default\n| (buffer) |\n+-------------+\n\n +----------------+\nfilter_in_1:default--[1x1 1:1 yuv420p]--default| Parsed_scale_0 |default--[4x2 1:2 yuv420p]--Parsed_overlay_2:main\n | (scale) |\n +----------------+\n\n +----------------+\nfilter_in_2:default--[1x1 1:1 yuv420p]--default| Parsed_scale_1 |default--[3x3 1:1 yuva420p]--Parsed_overlay_2:overlay\n | (scale) |\n +----------------+\n\n +------------------+\nParsed_scale_0:default--[4x2 1:2 yuv420p]------main| Parsed_overlay_2 |default--[2x2 1:1 yuv420p]--filter_out:default\nParsed_scale_1:default--[3x3 1:1 yuva420p]--overlay| (overlay) |\n +------------------+\n\n", fg.String()) + for _, command := range v.commands { + resp, err := fg.SendCommand(command.target, command.cmd, command.args, command.flags) + if command.withError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + require.Equal(t, command.resp, resp) + } + } // TODO Test BuffersrcAddFrame // TODO Test BuffersinkGetFrame diff --git a/filter_link.go b/filter_link.go index e5eef06..cf1e95c 100644 --- a/filter_link.go +++ b/filter_link.go @@ -15,6 +15,43 @@ func newFilterLinkFromC(c *C.AVFilterLink) *FilterLink { return &FilterLink{c: c} } +func (l *FilterLink) ChannelLayout() ChannelLayout { + v, _ := newChannelLayoutFromC(&l.c.ch_layout).clone() + return v +} + +func (l *FilterLink) FrameRate() Rational { + return newRationalFromC(l.c.frame_rate) +} + +func (l *FilterLink) Height() int { + return int(l.c.h) +} + +func (l *FilterLink) MediaType() MediaType { + return MediaType(l.c._type) +} + +func (l *FilterLink) PixelFormat() PixelFormat { + return PixelFormat(l.c.format) +} + +func (l *FilterLink) SampleAspectRatio() Rational { + return newRationalFromC(l.c.sample_aspect_ratio) +} + +func (l *FilterLink) SampleFormat() SampleFormat { + return SampleFormat(l.c.format) +} + +func (l *FilterLink) SampleRate() int { + return int(l.c.sample_rate) +} + func (l *FilterLink) TimeBase() Rational { return newRationalFromC(l.c.time_base) } + +func (l *FilterLink) Width() int { + return int(l.c.w) +}