diff --git a/filter.go b/filter.go index 8e8b133..9b22c2a 100644 --- a/filter.go +++ b/filter.go @@ -2,7 +2,9 @@ package astiav //#include import "C" -import "unsafe" +import ( + "unsafe" +) // https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavfilter/avfilter.h#L165 type Filter struct { @@ -29,3 +31,25 @@ func (f *Filter) Name() string { func (f *Filter) String() string { return f.Name() } + +func (f *Filter) NbInputs() int { + return int(f.c.nb_inputs) +} + +func (f *Filter) NbOutputs() int { + return int(f.c.nb_outputs) +} + +func (f *Filter) Inputs() (ps []*FilterPad) { + for idx := 0; idx < f.NbInputs(); idx++ { + ps = append(ps, newFilterPad(MediaType(C.avfilter_pad_get_type(f.c.inputs, C.int(idx))))) + } + return +} + +func (f *Filter) Outputs() (ps []*FilterPad) { + for idx := 0; idx < f.NbOutputs(); idx++ { + ps = append(ps, newFilterPad(MediaType(C.avfilter_pad_get_type(f.c.outputs, C.int(idx))))) + } + return +} diff --git a/filter_chain.go b/filter_chain.go new file mode 100644 index 0000000..9af6756 --- /dev/null +++ b/filter_chain.go @@ -0,0 +1,32 @@ +package astiav + +//#include +import "C" +import ( + "math" + "unsafe" +) + +// https://github.com/FFmpeg/FFmpeg/blob/n7.0/libavfilter/avfilter.h#L1142 +type FilterChain struct { + c *C.AVFilterChain +} + +func newFilterChainFromC(c *C.AVFilterChain) *FilterChain { + if c == nil { + return nil + } + return &FilterChain{c: c} +} + +func (fc *FilterChain) Filters() (fs []*FilterParams) { + fcs := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.AVFilterParams)(nil))](*C.AVFilterParams))(unsafe.Pointer(fc.c.filters)) + for i := 0; i < fc.NbFilters(); i++ { + fs = append(fs, newFilterParamsFromC(fcs[i])) + } + return +} + +func (fc *FilterChain) NbFilters() int { + return int(fc.c.nb_filters) +} diff --git a/filter_graph.go b/filter_graph.go index 7865589..abba8de 100644 --- a/filter_graph.go +++ b/filter_graph.go @@ -97,6 +97,16 @@ func (g *FilterGraph) Parse(content string, inputs, outputs *FilterInOut) error return newError(C.avfilter_graph_parse_ptr(g.c, cc, ic, oc, nil)) } +func (g *FilterGraph) ParseSegment(content string) (*FilterGraphSegment, error) { + cc := C.CString(content) + defer C.free(unsafe.Pointer(cc)) + var cs *C.AVFilterGraphSegment + if err := newError(C.avfilter_graph_segment_parse(g.c, cc, 0, &cs)); err != nil { + return nil, err + } + return newFilterGraphSegmentFromC(cs), nil +} + func (g *FilterGraph) Configure() error { return newError(C.avfilter_graph_config(g.c, nil)) } diff --git a/filter_graph_segment.go b/filter_graph_segment.go new file mode 100644 index 0000000..cb8716a --- /dev/null +++ b/filter_graph_segment.go @@ -0,0 +1,36 @@ +package astiav + +//#include +import "C" +import ( + "math" + "unsafe" +) + +// https://github.com/FFmpeg/FFmpeg/blob/n7.0/libavfilter/avfilter.h#L1156 +type FilterGraphSegment struct { + c *C.AVFilterGraphSegment +} + +func newFilterGraphSegmentFromC(c *C.AVFilterGraphSegment) *FilterGraphSegment { + if c == nil { + return nil + } + return &FilterGraphSegment{c: c} +} + +func (fgs *FilterGraphSegment) Free() { + C.avfilter_graph_segment_free(&fgs.c) +} + +func (fgs *FilterGraphSegment) Chains() (cs []*FilterChain) { + ccs := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.AVFilterChain)(nil))](*C.AVFilterChain))(unsafe.Pointer(fgs.c.chains)) + for i := 0; i < fgs.NbChains(); i++ { + cs = append(cs, newFilterChainFromC(ccs[i])) + } + return +} + +func (fgs *FilterGraphSegment) NbChains() int { + return int(fgs.c.nb_chains) +} diff --git a/filter_graph_test.go b/filter_graph_test.go index 1bdd356..b16ac8d 100644 --- a/filter_graph_test.go +++ b/filter_graph_test.go @@ -97,13 +97,13 @@ func TestFilterGraph(t *testing.T) { channelLayout: ChannelLayoutStereo, mediaType: MediaTypeAudio, sampleFormat: SampleFormatS16, - sampleRate: 4, + sampleRate: 3, 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", + content: "[input_1]aformat=sample_fmts=s16:channel_layouts=stereo:sample_rates=3,asettb=1/4", + s: " +---------------+\nParsed_asettb_1:default--[3Hz 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--[3Hz s16:stereo]--default| Parsed_aformat_0 |default--[3Hz s16:stereo]--Parsed_asettb_1:default\n | (aformat) |\n +------------------+\n\n +-----------------+\nParsed_aformat_0:default--[3Hz s16:stereo]--default| Parsed_asettb_1 |default--[3Hz s16:stereo]--filter_out:default\n | (asettb) |\n +-----------------+\n\n +------------------+\nfilter_in_1:default--[2Hz fltp:mono]--default| auto_aresample_0 |default--[3Hz s16:stereo]--Parsed_aformat_0:default\n | (aresample) |\n +------------------+\n\n", sources: []FilterArgs{ { "channel_layout": ChannelLayoutMono.String(), @@ -201,6 +201,26 @@ func TestFilterGraph(t *testing.T) { } } + fg2 := AllocFilterGraph() + require.NotNil(t, fg2) + defer fg2.Free() + fgs, err := fg2.ParseSegment("anullsrc") + require.NoError(t, err) + defer fgs.Free() + require.Equal(t, 1, fgs.NbChains()) + cs := fgs.Chains() + require.Equal(t, 1, len(cs)) + require.Equal(t, 1, cs[0].NbFilters()) + fs := cs[0].Filters() + require.Equal(t, 1, len(fs)) + f := FindFilterByName(fs[0].FilterName()) + require.NotNil(t, f) + require.Equal(t, 0, f.NbInputs()) + require.Equal(t, 1, f.NbOutputs()) + os := f.Outputs() + require.Equal(t, 1, len(os)) + require.Equal(t, MediaTypeAudio, os[0].MediaType()) + // TODO Test BuffersrcAddFrame // TODO Test BuffersinkGetFrame } diff --git a/filter_pad.go b/filter_pad.go new file mode 100644 index 0000000..8d0fcad --- /dev/null +++ b/filter_pad.go @@ -0,0 +1,14 @@ +package astiav + +// Struct attributes are internal but there are C functions to get some of them +type FilterPad struct { + mediaType MediaType +} + +func newFilterPad(mediaType MediaType) *FilterPad { + return &FilterPad{mediaType: mediaType} +} + +func (fp *FilterPad) MediaType() MediaType { + return fp.mediaType +} diff --git a/filter_params.go b/filter_params.go new file mode 100644 index 0000000..3d1fc51 --- /dev/null +++ b/filter_params.go @@ -0,0 +1,20 @@ +package astiav + +//#include +import "C" + +// https://github.com/FFmpeg/FFmpeg/blob/n7.0/libavfilter/avfilter.h#L1075 +type FilterParams struct { + c *C.AVFilterParams +} + +func newFilterParamsFromC(c *C.AVFilterParams) *FilterParams { + if c == nil { + return nil + } + return &FilterParams{c: c} +} + +func (fp *FilterParams) FilterName() string { + return C.GoString(fp.c.filter_name) +}