Skip to content

Commit

Permalink
Merge branch 'master' into l0rem1psum-feat-hardware-frame-ctx
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode authored Oct 23, 2024
2 parents e1f7d0c + cd2a16d commit 9777ac7
Show file tree
Hide file tree
Showing 32 changed files with 1,022 additions and 359 deletions.
4 changes: 4 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
@@ -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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Examples are located in the [examples](examples) directory and mirror as much as
|Custom IO Demuxing|[see](examples/custom_io_demuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/avio_reading.c)
|Demuxing/Decoding|[see](examples/demuxing_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/demuxing_decoding.c)
|Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/filtering_video.c)
|Frame data manipulation|[see](examples/frame_data_manipulation/main.go)|X
|Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/hw_decode.c)
|Hardware Encoding|[see](examples/hardware_encoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/vaapi_encode.c)
|Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/remuxing.c)
Expand Down Expand Up @@ -57,4 +58,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`.
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).
10 changes: 5 additions & 5 deletions astiav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type helperInput struct {
lastFrame *Frame
}

func (h *helper) inputFormatContext(name string) (fc *FormatContext, err error) {
func (h *helper) inputFormatContext(name string, ifmt *InputFormat) (fc *FormatContext, err error) {
h.m.Lock()
i, ok := h.inputs[name]
if ok && i.formatContext != nil {
Expand All @@ -65,7 +65,7 @@ func (h *helper) inputFormatContext(name string) (fc *FormatContext, err error)
}
h.closer.Add(fc.Free)

if err = fc.OpenInput("testdata/"+name, nil, nil); err != nil {
if err = fc.OpenInput("testdata/"+name, ifmt, nil); err != nil {
err = fmt.Errorf("astiav_test: opening input failed: %w", err)
return
}
Expand Down Expand Up @@ -95,7 +95,7 @@ func (h *helper) inputFirstPacket(name string) (pkt *Packet, err error) {
h.m.Unlock()

var fc *FormatContext
if fc, err = h.inputFormatContext(name); err != nil {
if fc, err = h.inputFormatContext(name, nil); err != nil {
err = fmt.Errorf("astiav_test: getting input format context failed")
return
}
Expand All @@ -118,7 +118,7 @@ func (h *helper) inputFirstPacket(name string) (pkt *Packet, err error) {
return
}

func (h *helper) inputLastFrame(name string, mediaType MediaType) (f *Frame, err error) {
func (h *helper) inputLastFrame(name string, mediaType MediaType, ifmt *InputFormat) (f *Frame, err error) {
h.m.Lock()
i, ok := h.inputs[name]
if ok && i.lastFrame != nil {
Expand All @@ -128,7 +128,7 @@ func (h *helper) inputLastFrame(name string, mediaType MediaType) (f *Frame, err
h.m.Unlock()

var fc *FormatContext
if fc, err = h.inputFormatContext(name); err != nil {
if fc, err = h.inputFormatContext(name, ifmt); err != nil {
err = fmt.Errorf("astiav_test: getting input format context failed: %w", err)
return
}
Expand Down
13 changes: 11 additions & 2 deletions bit_stream_filter_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,17 @@ func (bsfc *BitStreamFilterContext) ReceivePacket(p *Packet) error {
}

func (bsfc *BitStreamFilterContext) Free() {
classers.del(bsfc)
C.av_bsf_free(&bsfc.c)
if bsfc.c != nil {
// Make sure to clone the classer before freeing the object since
// the C free method may reset the pointer
c := newClonedClasser(bsfc)
C.av_bsf_free(&bsfc.c)
// Make sure to remove from classers after freeing the object since
// the C free method may use methods needing the classer
if c != nil {
classers.del(c)
}
}
}

func (bsfc *BitStreamFilterContext) InputTimeBase() Rational {
Expand Down
20 changes: 20 additions & 0 deletions class.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type Classer interface {
Class() *Class
}

var _ Classer = (*UnknownClasser)(nil)

type UnknownClasser struct {
c *Class
}
Expand All @@ -64,6 +66,24 @@ func (c *UnknownClasser) Class() *Class {
return c.c
}

var _ Classer = (*ClonedClasser)(nil)

type ClonedClasser struct {
c *Class
}

func newClonedClasser(c Classer) *ClonedClasser {
cl := c.Class()
if cl == nil {
return nil
}
return &ClonedClasser{c: newClassFromC(cl.ptr)}
}

func (c *ClonedClasser) Class() *Class {
return c.c
}

var classers = newClasserPool()

type classerPool struct {
Expand Down
30 changes: 21 additions & 9 deletions class_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,48 @@ func TestClass(t *testing.T) {

func TestClassers(t *testing.T) {
cl := len(classers.p)
f := AllocFilterGraph()
f1 := AllocFilterGraph()
f2 := AllocFilterGraph()
c := FindDecoder(CodecIDMjpeg)
require.NotNil(t, c)
bf := FindBitStreamFilterByName("null")
require.NotNil(t, bf)
bfc, err := AllocBitStreamFilterContext(bf)
require.NoError(t, err)
cc := AllocCodecContext(c)
require.NotNil(t, cc)
bufferSink := FindFilterByName("buffersink")
require.NotNil(t, bufferSink)
fc, err := f.NewFilterContext(bufferSink, "filter_out", nil)
fc1, err := f1.NewFilterContext(bufferSink, "filter_out", nil)
require.NoError(t, err)
_, err = f2.NewFilterContext(bufferSink, "filter_out", nil)
require.NoError(t, err)
fmc1 := AllocFormatContext()
fmc2 := AllocFormatContext()
require.NoError(t, fmc2.OpenInput("testdata/video.mp4", nil, nil))
path := filepath.Join(t.TempDir(), "iocontext.txt")
ic, err := OpenIOContext(path, NewIOContextFlags(IOContextFlagWrite))
ic1, err := OpenIOContext(path, NewIOContextFlags(IOContextFlagWrite))
require.NoError(t, err)
defer os.RemoveAll(path)
ic2, err := AllocIOContext(1, true, nil, nil, nil)
require.NoError(t, err)
ssc, err := CreateSoftwareScaleContext(1, 1, PixelFormatRgba, 2, 2, PixelFormatRgba, NewSoftwareScaleContextFlags())
require.NoError(t, err)

require.Equal(t, cl+8, len(classers.p))
v, ok := classers.get(unsafe.Pointer(f.c))
require.Equal(t, cl+12, len(classers.p))
v, ok := classers.get(unsafe.Pointer(f1.c))
require.True(t, ok)
require.Equal(t, f, v)
require.Equal(t, f1, v)

bfc.Free()
cc.Free()
fc.Free()
f.Free()
fc1.Free()
f1.Free()
f2.Free()
fmc1.Free()
fmc2.CloseInput()
require.NoError(t, ic.Close())
require.NoError(t, ic1.Close())
ic2.Free()
ssc.Free()
require.Equal(t, cl, len(classers.p))
}
13 changes: 11 additions & 2 deletions codec_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@ func (cc *CodecContext) Free() {
C.av_buffer_unref(&cc.hfc.c)
cc.hfc = nil
}
classers.del(cc)
C.avcodec_free_context(&cc.c)
if cc.c != nil {
// Make sure to clone the classer before freeing the object since
// the C free method may reset the pointer
c := newClonedClasser(cc)
C.avcodec_free_context(&cc.c)
// Make sure to remove from classers after freeing the object since
// the C free method may use methods needing the classer
if c != nil {
classers.del(c)
}
}
}

func (cc *CodecContext) String() string {
Expand Down
2 changes: 1 addition & 1 deletion codec_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestCodecContext(t *testing.T) {
fc, err := globalHelper.inputFormatContext("video.mp4")
fc, err := globalHelper.inputFormatContext("video.mp4", nil)
require.NoError(t, err)
ss := fc.Streams()
require.Len(t, ss, 2)
Expand Down
2 changes: 1 addition & 1 deletion codec_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestCodecParameters(t *testing.T) {
fc, err := globalHelper.inputFormatContext("video.mp4")
fc, err := globalHelper.inputFormatContext("video.mp4", nil)
require.NoError(t, err)
ss := fc.Streams()
require.Len(t, ss, 2)
Expand Down
33 changes: 2 additions & 31 deletions examples/demuxing_decoding/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"flag"
"fmt"
"image"
"log"
"strings"

Expand Down Expand Up @@ -107,7 +106,6 @@ func main() {
}

// Loop through packets
var i image.Image
for {
// Read frame
if err := inputFormatContext.ReadFrame(pkt); err != nil {
Expand Down Expand Up @@ -138,35 +136,8 @@ func main() {
log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err))
}

// Do something with decoded frame
if s.inputStream.CodecParameters().MediaType() == astiav.MediaTypeVideo {
// In this example, we'll process the frame data but you can do whatever you feel like
// with the decoded frame
fd := f.Data()

// Image has not yet been initialized
// If the image format can change in the stream, you'll need to guess image format for every frame
if i == nil {
// Guess image format
// It might not return an image.Image in the proper format for your use case, in that case
// you can skip this step and provide .ToImage() with your own image.Image
var err error
if i, err = fd.GuessImageFormat(); err != nil {
log.Fatal(fmt.Errorf("main: guessing image format failed: %w", err))
}
}

// Copy frame data to the image
if err := fd.ToImage(i); err != nil {
log.Fatal(fmt.Errorf("main: copying frame data to the image failed: %w", err))
}

// Log
log.Printf("new video frame: stream %d - pts: %d - size: %dx%d - color at (0,0): %+v", pkt.StreamIndex(), f.Pts(), i.Bounds().Dx(), i.Bounds().Dy(), i.At(0, 0))
} else {
// Log
log.Printf("new audio frame: stream %d - pts: %d", pkt.StreamIndex(), f.Pts())
}
// Log
log.Printf("new %s frame: stream %d - pts: %d", s.inputStream.CodecParameters().MediaType(), pkt.StreamIndex(), f.Pts())
}
}

Expand Down
16 changes: 8 additions & 8 deletions examples/filtering/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand All @@ -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)

Expand All @@ -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
}
Expand All @@ -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
Expand Down
Loading

0 comments on commit 9777ac7

Please sign in to comment.