diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md new file mode 100644 index 0000000..98c047b --- /dev/null +++ b/BREAKING_CHANGES.md @@ -0,0 +1,4 @@ +# v0.24.0 + +- use `FilterGraph`.`NewBuffersinkFilterContext` and `FilterGraph`.`NewBuffersrcFilterContext` instead of `FilterGraph`.`NewFilterContext` when creating `buffersink` and `buffersrc` filter contexts and use `BuffersinkFilterContext`.`GetFrame` and `BuffersrcFilterContext`.`AddFrame` to manipulate them. Use `BuffersinkFilterContext`.`FilterContext` and `BuffersrcFilterContext`.`FilterContext` in `FilterInOut`.`SetFilterContext`. +- `FilterLink` has been removed and methods like `BuffersinkFilterContext`.`ChannelLayout` have been added instead \ No newline at end of file diff --git a/README.md b/README.md index 8483e5a..8d76df3 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,8 @@ export PKG_CONFIG_PATH="{{ path to your working directory }}/tmp/n7.0/lib/pkgcon # Why astiav? -After maintaining for several years the most starred [fork](https://github.com/asticode/goav) of [goav](https://github.com/giorgisio/goav), I've decided to write from scratch my own C bindings to fix most of the problems I still encountered using `goav`. \ No newline at end of file +After maintaining for several years the most starred [fork](https://github.com/asticode/goav) of [goav](https://github.com/giorgisio/goav), I've decided to write from scratch my own C bindings to fix most of the problems I still encountered using `goav`. + +# Breaking changes + +You can see the list of breaking changes [here](BREAKING_CHANGES.md). \ No newline at end of file diff --git a/examples/filtering/main.go b/examples/filtering/main.go index 9090fc0..993bd7f 100644 --- a/examples/filtering/main.go +++ b/examples/filtering/main.go @@ -23,8 +23,8 @@ var ( ) type stream struct { - buffersinkContext *astiav.FilterContext - buffersrcContext *astiav.FilterContext + buffersinkContext *astiav.BuffersinkFilterContext + buffersrcContext *astiav.BuffersrcFilterContext decCodec *astiav.Codec decCodecContext *astiav.CodecContext decFrame *astiav.Frame @@ -232,7 +232,7 @@ func initFilter() (err error) { } // Create filter contexts - if s.buffersrcContext, err = s.filterGraph.NewFilterContext(buffersrc, "in", astiav.FilterArgs{ + if s.buffersrcContext, err = s.filterGraph.NewBuffersrcFilterContext(buffersrc, "in", astiav.FilterArgs{ "pix_fmt": strconv.Itoa(int(s.decCodecContext.PixelFormat())), "pixel_aspect": s.decCodecContext.SampleAspectRatio().String(), "time_base": s.inputStream.TimeBase().String(), @@ -241,20 +241,20 @@ func initFilter() (err error) { err = fmt.Errorf("main: creating buffersrc context failed: %w", err) return } - if s.buffersinkContext, err = s.filterGraph.NewFilterContext(buffersink, "in", nil); err != nil { + if s.buffersinkContext, err = s.filterGraph.NewBuffersinkFilterContext(buffersink, "in", nil); err != nil { err = fmt.Errorf("main: creating buffersink context failed: %w", err) return } // Update outputs outputs.SetName("in") - outputs.SetFilterContext(s.buffersrcContext) + outputs.SetFilterContext(s.buffersrcContext.FilterContext()) outputs.SetPadIdx(0) outputs.SetNext(nil) // Update inputs inputs.SetName("out") - inputs.SetFilterContext(s.buffersinkContext) + inputs.SetFilterContext(s.buffersinkContext.FilterContext()) inputs.SetPadIdx(0) inputs.SetNext(nil) @@ -278,7 +278,7 @@ func initFilter() (err error) { func filterFrame(f *astiav.Frame, s *stream) (err error) { // Add frame - if err = s.buffersrcContext.BuffersrcAddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { + if err = s.buffersrcContext.AddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { err = fmt.Errorf("main: adding frame failed: %w", err) return } @@ -289,7 +289,7 @@ func filterFrame(f *astiav.Frame, s *stream) (err error) { s.filterFrame.Unref() // Get frame - if err = s.buffersinkContext.BuffersinkGetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { + if err = s.buffersinkContext.GetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { err = nil break diff --git a/examples/transcoding/main.go b/examples/transcoding/main.go index 6eb40e4..f1024ba 100644 --- a/examples/transcoding/main.go +++ b/examples/transcoding/main.go @@ -25,8 +25,8 @@ var ( ) type stream struct { - buffersinkContext *astiav.FilterContext - buffersrcContext *astiav.FilterContext + buffersinkContext *astiav.BuffersinkFilterContext + buffersrcContext *astiav.BuffersrcFilterContext decCodec *astiav.Codec decCodecContext *astiav.CodecContext decFrame *astiav.Frame @@ -395,24 +395,24 @@ func initFilters() (err error) { } // Create filter contexts - if s.buffersrcContext, err = s.filterGraph.NewFilterContext(buffersrc, "in", args); err != nil { + if s.buffersrcContext, err = s.filterGraph.NewBuffersrcFilterContext(buffersrc, "in", args); err != nil { err = fmt.Errorf("main: creating buffersrc context failed: %w", err) return } - if s.buffersinkContext, err = s.filterGraph.NewFilterContext(buffersink, "out", nil); err != nil { + if s.buffersinkContext, err = s.filterGraph.NewBuffersinkFilterContext(buffersink, "out", nil); err != nil { err = fmt.Errorf("main: creating buffersink context failed: %w", err) return } // Update outputs outputs.SetName("in") - outputs.SetFilterContext(s.buffersrcContext) + outputs.SetFilterContext(s.buffersrcContext.FilterContext()) outputs.SetPadIdx(0) outputs.SetNext(nil) // Update inputs inputs.SetName("out") - inputs.SetFilterContext(s.buffersinkContext) + inputs.SetFilterContext(s.buffersinkContext.FilterContext()) inputs.SetPadIdx(0) inputs.SetNext(nil) @@ -441,7 +441,7 @@ func initFilters() (err error) { func filterEncodeWriteFrame(f *astiav.Frame, s *stream) (err error) { // Add frame - if err = s.buffersrcContext.BuffersrcAddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { + if err = s.buffersrcContext.AddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { err = fmt.Errorf("main: adding frame failed: %w", err) return } @@ -452,7 +452,7 @@ func filterEncodeWriteFrame(f *astiav.Frame, s *stream) (err error) { s.filterFrame.Unref() // Get frame - if err = s.buffersinkContext.BuffersinkGetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { + if err = s.buffersinkContext.GetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { err = nil break diff --git a/filter_context.go b/filter_context.go index fca79ae..d91361b 100644 --- a/filter_context.go +++ b/filter_context.go @@ -6,7 +6,6 @@ package astiav //#include import "C" import ( - "math" "unsafe" ) @@ -38,46 +37,96 @@ func (fc *FilterContext) Free() { } } -func (fc *FilterContext) BuffersrcAddFrame(f *Frame, fs BuffersrcFlags) error { - var cf *C.AVFrame - if f != nil { - cf = f.c - } - return newError(C.av_buffersrc_add_frame_flags(fc.c, cf, C.int(fs))) +func (fc *FilterContext) Class() *Class { + return newClassFromC(unsafe.Pointer(fc.c)) +} + +type BuffersinkFilterContext struct { + fc *FilterContext +} + +func newBuffersinkFilterContext(fc *FilterContext) *BuffersinkFilterContext { + return &BuffersinkFilterContext{fc: fc} +} + +func (bfc *BuffersinkFilterContext) ChannelLayout() ChannelLayout { + var cl C.AVChannelLayout + C.av_buffersink_get_ch_layout(bfc.fc.c, &cl) + return newChannelLayoutFromC(&cl) +} + +func (bfc *BuffersinkFilterContext) ColorRange() ColorRange { + return ColorRange(C.av_buffersink_get_color_range(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) ColorSpace() ColorSpace { + return ColorSpace(C.av_buffersink_get_colorspace(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) FilterContext() *FilterContext { + return bfc.fc +} + +func (bfc *BuffersinkFilterContext) FrameRate() Rational { + return newRationalFromC(C.av_buffersink_get_frame_rate(bfc.fc.c)) } -func (fc *FilterContext) BuffersinkGetFrame(f *Frame, fs BuffersinkFlags) error { +func (bfc *BuffersinkFilterContext) GetFrame(f *Frame, fs BuffersinkFlags) error { var cf *C.AVFrame if f != nil { cf = f.c } - return newError(C.av_buffersink_get_frame_flags(fc.c, cf, C.int(fs))) + return newError(C.av_buffersink_get_frame_flags(bfc.fc.c, cf, C.int(fs))) } -func (fc *FilterContext) Class() *Class { - return newClassFromC(unsafe.Pointer(fc.c)) +func (bfc *BuffersinkFilterContext) Height() int { + return int(C.av_buffersink_get_h(bfc.fc.c)) } -func (fc *FilterContext) NbInputs() int { - return int(fc.c.nb_inputs) +func (bfc *BuffersinkFilterContext) MediaType() MediaType { + return MediaType(C.av_buffersink_get_type(bfc.fc.c)) } -func (fc *FilterContext) NbOutputs() int { - return int(fc.c.nb_outputs) +func (bfc *BuffersinkFilterContext) PixelFormat() PixelFormat { + return PixelFormat(C.av_buffersink_get_format(bfc.fc.c)) } -func (fc *FilterContext) Inputs() (ls []*FilterLink) { - lcs := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.AVFilterLink)(nil))](*C.AVFilterLink))(unsafe.Pointer(fc.c.inputs)) - for i := 0; i < fc.NbInputs(); i++ { - ls = append(ls, newFilterLinkFromC(lcs[i])) - } - return +func (bfc *BuffersinkFilterContext) SampleAspectRatio() Rational { + return newRationalFromC(C.av_buffersink_get_sample_aspect_ratio(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) SampleFormat() SampleFormat { + return SampleFormat(C.av_buffersink_get_format(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) SampleRate() int { + return int(C.av_buffersink_get_sample_rate(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) TimeBase() Rational { + return newRationalFromC(C.av_buffersink_get_time_base(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) Width() int { + return int(C.av_buffersink_get_w(bfc.fc.c)) +} + +type BuffersrcFilterContext struct { + fc *FilterContext +} + +func newBuffersrcFilterContext(fc *FilterContext) *BuffersrcFilterContext { + return &BuffersrcFilterContext{fc: fc} } -func (fc *FilterContext) Outputs() (ls []*FilterLink) { - lcs := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.AVFilterLink)(nil))](*C.AVFilterLink))(unsafe.Pointer(fc.c.outputs)) - for i := 0; i < fc.NbOutputs(); i++ { - ls = append(ls, newFilterLinkFromC(lcs[i])) +func (bfc *BuffersrcFilterContext) AddFrame(f *Frame, fs BuffersrcFlags) error { + var cf *C.AVFrame + if f != nil { + cf = f.c } - return + return newError(C.av_buffersrc_add_frame_flags(bfc.fc.c, cf, C.int(fs))) +} + +func (bfc *BuffersrcFilterContext) FilterContext() *FilterContext { + return bfc.fc } diff --git a/filter_graph.go b/filter_graph.go index b624bcb..077e87d 100644 --- a/filter_graph.go +++ b/filter_graph.go @@ -103,6 +103,22 @@ func (g *FilterGraph) NewFilterContext(f *Filter, name string, args FilterArgs) return fc, nil } +func (g *FilterGraph) NewBuffersinkFilterContext(f *Filter, name string, args FilterArgs) (*BuffersinkFilterContext, error) { + fc, err := g.NewFilterContext(f, name, args) + if err != nil { + return nil, err + } + return newBuffersinkFilterContext(fc), nil +} + +func (g *FilterGraph) NewBuffersrcFilterContext(f *Filter, name string, args FilterArgs) (*BuffersrcFilterContext, error) { + fc, err := g.NewFilterContext(f, name, args) + if err != nil { + return nil, err + } + return newBuffersrcFilterContext(fc), nil +} + func (g *FilterGraph) Parse(content string, inputs, outputs *FilterInOut) error { cc := C.CString(content) defer C.free(unsafe.Pointer(cc)) diff --git a/filter_graph_test.go b/filter_graph_test.go index 14dceee..8f4bd5f 100644 --- a/filter_graph_test.go +++ b/filter_graph_test.go @@ -31,13 +31,14 @@ func TestFilterGraph(t *testing.T) { target string withError bool } - type link struct { + type buffersink struct { channelLayout ChannelLayout colorRange ColorRange colorSpace ColorSpace frameRate Rational height int mediaType MediaType + name string pixelFormat PixelFormat sampleAspectRatio Rational sampleFormat SampleFormat @@ -45,30 +46,32 @@ func TestFilterGraph(t *testing.T) { timeBase Rational width int } + type buffersrc struct { + name string + } type graph struct { - buffersinkExpectedInput link - buffersinkName string - buffersrcName string - commands []command - content string - s string - sources []FilterArgs + buffersink buffersink + buffersrc buffersrc + commands []command + content string + s string + sources []FilterArgs } for _, v := range []graph{ { - buffersinkExpectedInput: link{ + buffersink: buffersink{ colorRange: ColorRangeUnspecified, colorSpace: ColorSpaceUnspecified, frameRate: NewRational(4, 1), height: 8, mediaType: MediaTypeVideo, + name: "buffersink", pixelFormat: PixelFormatYuv420P, sampleAspectRatio: NewRational(2, 1), timeBase: NewRational(1, 4), width: 4, }, - buffersinkName: "buffersink", - buffersrcName: "buffer", + buffersrc: buffersrc{name: "buffer"}, commands: []command{ { args: "a", @@ -97,17 +100,17 @@ func TestFilterGraph(t *testing.T) { }, }, { - buffersinkExpectedInput: link{ + buffersink: buffersink{ channelLayout: ChannelLayoutStereo, mediaType: MediaTypeAudio, + name: "abuffersink", sampleFormat: SampleFormatS16, sampleRate: 3, timeBase: NewRational(1, 4), }, - buffersinkName: "abuffersink", - buffersrcName: "abuffer", - 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", + buffersrc: buffersrc{name: "abuffer"}, + 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(), @@ -122,21 +125,21 @@ func TestFilterGraph(t *testing.T) { require.NotNil(t, fg) defer fg.Free() - buffersrc := FindFilterByName(v.buffersrcName) + buffersrc := FindFilterByName(v.buffersrc.name) require.NotNil(t, buffersrc) - buffersink := FindFilterByName(v.buffersinkName) + buffersink := FindFilterByName(v.buffersink.name) require.NotNil(t, buffersink) - buffersinkContext, err := fg.NewFilterContext(buffersink, "filter_out", nil) + buffersinkContext, err := fg.NewBuffersinkFilterContext(buffersink, "filter_out", nil) require.NoError(t, err) - cl = buffersinkContext.Class() + cl = buffersinkContext.FilterContext().Class() require.NotNil(t, cl) require.Equal(t, "AVFilter", cl.Name()) inputs := AllocFilterInOut() defer inputs.Free() inputs.SetName("out") - inputs.SetFilterContext(buffersinkContext) + inputs.SetFilterContext(buffersinkContext.FilterContext()) inputs.SetPadIdx(0) inputs.SetNext(nil) @@ -147,15 +150,15 @@ func TestFilterGraph(t *testing.T) { } }() - var buffersrcContexts []*FilterContext + var buffersrcContexts []*BuffersrcFilterContext for idx, src := range v.sources { - buffersrcContext, err := fg.NewFilterContext(buffersrc, fmt.Sprintf("filter_in_%d", idx+1), src) + buffersrcContext, err := fg.NewBuffersrcFilterContext(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.SetFilterContext(buffersrcContext.FilterContext()) o.SetPadIdx(0) o.SetNext(outputs) @@ -165,33 +168,21 @@ func TestFilterGraph(t *testing.T) { 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 { + require.Equal(t, v.buffersink.frameRate, buffersinkContext.FrameRate()) + require.Equal(t, v.buffersink.mediaType, buffersinkContext.MediaType()) + require.Equal(t, v.buffersink.timeBase, buffersinkContext.TimeBase()) + switch v.buffersink.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()) + require.True(t, v.buffersink.channelLayout.Equal(buffersinkContext.ChannelLayout())) + require.Equal(t, v.buffersink.sampleFormat, buffersinkContext.SampleFormat()) + require.Equal(t, v.buffersink.sampleRate, buffersinkContext.SampleRate()) default: - require.Equal(t, e.colorRange, g.ColorRange()) - require.Equal(t, e.colorSpace, g.ColorSpace()) - 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()) - } - - 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()) + require.Equal(t, v.buffersink.colorRange, buffersinkContext.ColorRange()) + require.Equal(t, v.buffersink.colorSpace, buffersinkContext.ColorSpace()) + require.Equal(t, v.buffersink.height, buffersinkContext.Height()) + require.Equal(t, v.buffersink.pixelFormat, buffersinkContext.PixelFormat()) + require.Equal(t, v.buffersink.sampleAspectRatio, buffersinkContext.SampleAspectRatio()) + require.Equal(t, v.buffersink.width, buffersinkContext.Width()) } require.Equal(t, v.s, fg.String()) diff --git a/filter_link.go b/filter_link.go deleted file mode 100644 index a94929c..0000000 --- a/filter_link.go +++ /dev/null @@ -1,65 +0,0 @@ -package astiav - -//#include -import "C" - -// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavfilter/avfilter.h#L471 -type FilterLink struct { - c *C.AVFilterLink -} - -func newFilterLinkFromC(c *C.AVFilterLink) *FilterLink { - if c == nil { - return nil - } - return &FilterLink{c: c} -} - -func (l *FilterLink) ChannelLayout() ChannelLayout { - v, _ := newChannelLayoutFromC(&l.c.ch_layout).clone() - return v -} - -func (l *FilterLink) ColorRange() ColorRange { - return ColorRange(l.c.color_range) -} - -func (l *FilterLink) ColorSpace() ColorSpace { - return ColorSpace(l.c.colorspace) -} - -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) -}