diff --git a/libav/frame_filler.go b/libav/frame_filler.go new file mode 100644 index 0000000..5e240f4 --- /dev/null +++ b/libav/frame_filler.go @@ -0,0 +1,82 @@ +package astilibav + +import ( + "fmt" + + "github.com/asticode/go-astiav" + "github.com/asticode/go-astiencoder" + "github.com/asticode/go-astikit" +) + +type FrameFiller struct { + eh *astiencoder.EventHandler + fallbackFrame *astiav.Frame + previousFrame *astiav.Frame + previousNode astiencoder.Node + onPuts []func(f *astiav.Frame, n astiencoder.Node) + p *framePool + target interface{} +} + +func NewFrameFiller(c *astikit.Closer, eh *astiencoder.EventHandler, target interface{}) *FrameFiller { + return &FrameFiller{ + eh: eh, + p: newFramePool(c), + target: target, + } +} + +func (ff *FrameFiller) WithFallbackFrame(a FrameAdapter) (dst *FrameFiller, err error) { + // Create dst + dst = ff + + // Get frame + f := ff.p.get() + + // Adapt frame + if err = a(f); err != nil { + err = fmt.Errorf("astilibav: adapting frame failed: %w", err) + return + } + + // Store frame + ff.fallbackFrame = f + return +} + +func (ff *FrameFiller) WithPreviousFrame() *FrameFiller { + // Add on put + ff.onPuts = append(ff.onPuts, func(f *astiav.Frame, n astiencoder.Node) { + // Store node + ff.previousNode = n + + // Create frame + if ff.previousFrame == nil { + ff.previousFrame = ff.p.get() + } else { + ff.previousFrame.Unref() + } + + // Copy frame + if err := ff.previousFrame.Ref(f); err != nil { + emitError(ff.target, ff.eh, err, "refing frame") + ff.p.put(ff.previousFrame) + ff.previousFrame = nil + } + }) + return ff +} + +func (ff *FrameFiller) Get() (*astiav.Frame, astiencoder.Node) { + if ff.previousFrame != nil { + return ff.previousFrame, ff.previousNode + } + return ff.fallbackFrame, nil +} + +func (ff *FrameFiller) Put(f *astiav.Frame, n astiencoder.Node) { + // Loop through on puts + for _, onPut := range ff.onPuts { + onPut(f, n) + } +} diff --git a/libav/frame_filler_test.go b/libav/frame_filler_test.go new file mode 100644 index 0000000..0a32887 --- /dev/null +++ b/libav/frame_filler_test.go @@ -0,0 +1,57 @@ +package astilibav + +import ( + "testing" + + "github.com/asticode/go-astiav" + "github.com/asticode/go-astiencoder" + "github.com/asticode/go-astikit" + "github.com/stretchr/testify/require" +) + +func TestFrameFiller(t *testing.T) { + c := astikit.NewCloser() + defer c.Close() + eh := astiencoder.NewEventHandler() + var logs []string + eh.AddForEventName(astiencoder.EventNameError, func(e astiencoder.Event) (deleteListener bool) { + t.Log(e.Payload) + logs = append(logs, e.Payload.(error).Error()) + return + }) + ff := NewFrameFiller(c, eh, nil) + + f, n := ff.Get() + require.Nil(t, f) + require.Nil(t, n) + + ff.WithPreviousFrame() + _, err := ff.WithFallbackFrame(EmptyVideoFrameAdapter(astiav.ColorRangeUnspecified, astiav.PixelFormatYuv420P, 4, 2)) + require.NoError(t, err) + + f, n = ff.Get() + require.NotNil(t, f) + require.Equal(t, 4, f.Width()) + require.Nil(t, n) + + nf := astiav.AllocFrame() + defer nf.Free() + ff.Put(nf, nil) + require.Equal(t, []string{"astilibav: refing frame failed: Invalid argument"}, logs) + f, n = ff.Get() + require.NotNil(t, f) + require.Equal(t, 4, f.Width()) + require.Nil(t, n) + + err = EmptyVideoFrameAdapter(astiav.ColorRangeUnspecified, astiav.PixelFormatYuv420P, 6, 2)(nf) + require.NoError(t, err) + nn := &Filterer{} + ff.Put(nf, nn) + + for i := 0; i <= 1; i++ { + f, n = ff.Get() + require.NotNil(t, f) + require.Equal(t, 6, f.Width()) + require.Equal(t, nn, n) + } +}