AVFilter, examples

This commit is contained in:
alex
2019-01-07 17:32:25 +03:00
parent 630c9bc443
commit 91aa3b021c
7 changed files with 159 additions and 425 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
*.mpg
*.exe
!examples/tests-sample.mp4
!examples/bbb.mp4
remuxing
transcode
*.ts

View File

@@ -590,3 +590,26 @@ func (cc *CodecCtx) Encode(frames []*Frame, drain int) ([]*Packet, error) {
return result, nil
}
func (cc *CodecCtx) Decode2(pkt *Packet) (*Frame, int) {
var (
ret int
)
if pkt == nil {
ret = int(C.avcodec_send_packet(cc.avCodecCtx, nil))
} else {
ret = int(C.avcodec_send_packet(cc.avCodecCtx, &pkt.avPacket))
}
if ret < 0 {
return nil, ret
}
frame := NewFrame()
if ret = int(C.avcodec_receive_frame(cc.avCodecCtx, frame.avFrame)); ret < 0 {
return nil, ret
}
return frame, 0
}

BIN
examples/bbb.mp4 Normal file

Binary file not shown.

View File

@@ -3,9 +3,9 @@ package main
import (
"errors"
"flag"
"fmt"
"log"
"strings"
"syscall"
"github.com/3d0c/gmf"
)
@@ -22,8 +22,9 @@ func (i *arrayFlags) Set(value string) error {
}
type Input struct {
ctx *gmf.FmtCtx
finished bool
ctx *gmf.FmtCtx
lastFrame *gmf.Frame
finished bool
}
var (
@@ -88,9 +89,9 @@ func addStream(codecName string, oc *gmf.FmtCtx, ist *gmf.Stream) (int, int) {
cc.SetTimeBase(gmf.AVR{1, 25})
ost.SetTimeBase(gmf.AVR{1, 25})
cc.SetProfile(gmf.FF_PROFILE_MPEG4_SIMPLE)
fmt.Printf("setup dims: %d, %d\n", ist.CodecCtx().Width(), ist.CodecCtx().Height())
cc.SetDimension(ist.CodecCtx().Width(), ist.CodecCtx().Height())
cc.SetPixFmt(ist.CodecCtx().PixFmt())
// cc.SetOptions([]gmf.Option{gmf.Option{Key: "b", Val: 100000}})
}
if err := cc.Open(nil); err != nil {
@@ -116,7 +117,7 @@ func main() {
flag.Parse()
if len(src) == 0 || dst == "" {
log.Fatal("at least one source and destination required")
log.Fatal("at least one source and destination required, e.g.\n./watermark -src=bbb.mp4 -src=test.png -dst=overlay.mp4")
}
octx, err := gmf.NewOutputCtx(dst)
@@ -135,7 +136,7 @@ func main() {
inputs[i] = &Input{}
inputs[i].ctx = ictx
log.Printf("src[%d]=%s\n", i, name)
log.Printf("Source #%d - %s\n", i, name)
}
srcVideoStream, err := inputs[0].ctx.GetBestStream(gmf.AVMEDIA_TYPE_VIDEO)
@@ -172,24 +173,23 @@ func main() {
*/
var (
i int = 0
pkt *gmf.Packet
ist *gmf.Stream
ost *gmf.Stream
i, ret int = 0, 0
pkt *gmf.Packet
ist *gmf.Stream
ost *gmf.Stream
)
for i, stream := range srcStreams {
fmt.Printf("srcStreams[%d] - %s, %d\n", i, stream.CodecCtx().Codec().LongName(), stream.CodecCtx().Width())
log.Printf("stream #%d - %s, %s\n", i, stream.CodecCtx().Codec().LongName(), stream.CodecCtx().GetVideoSize())
}
filtered := make([]*gmf.Frame, 0)
ost, err = octx.GetStream(0)
if err != nil {
log.Fatalf("can't get stream - %s\n", err)
}
filter, err := gmf.NewFilter("overlay=10:main_h-overlay_h-10", srcStreams, ost, options)
defer filter.Release()
if err != nil {
log.Fatalf("%s\n", err)
}
@@ -198,21 +198,16 @@ func main() {
log.Fatalf("error writing header - %s\n", err)
}
fmt.Printf("ost TimeBase: %v, %s\n", ost.TimeBase(), ost.CodecCtx().Codec().LongName())
total := 0
init := false
for {
total++
fmt.Printf("Total: %d\n", total)
if total == 30 {
break
}
fmt.Printf("i=%d\n", i)
var (
frame *gmf.Frame
ff []*gmf.Frame
)
for {
if finishedNb() == len(inputs) {
log.Printf("finished all\n")
log.Printf("Finished all\n")
break
}
@@ -221,7 +216,6 @@ func main() {
}
if inputs[i].finished {
fmt.Printf("inputs[%d] - finished\n", i)
i++
continue
}
@@ -232,8 +226,12 @@ func main() {
if err != nil && err.Error() != "End of file" {
log.Fatalf("error getting next packet - %s", err)
} else if err != nil && pkt == nil {
fmt.Printf("continue getting next packets - %s\n", err)
inputs[i].finished = true
if !inputs[i].finished {
log.Printf("EOF input #%d, closing\n", i)
filter.RequestOldest()
filter.Close(i)
inputs[i].finished = true
}
i++
continue
}
@@ -247,17 +245,17 @@ func main() {
continue
}
frames, err := ist.CodecCtx().Decode(pkt)
if err != nil && err.Error() != "End of file" {
log.Fatalf("error decoding - %s\n", err)
} else if err != nil {
log.Printf("error decoding pkt - %s\n", err)
frame, ret = ist.CodecCtx().Decode2(pkt)
if ret < 0 && gmf.AvErrno(ret) == syscall.EAGAIN {
continue
} else if ret == gmf.AVERROR_EOF {
log.Fatalf("EOF in Decode2, handle it\n")
} else if ret < 0 {
log.Fatalf("Unexpected error - %s\n", gmf.AvError(ret))
}
fmt.Printf("len(frames) = %d\n", len(frames))
if len(frames) > 0 && !init {
if err := filter.AddFrame(frames[0], i); err != nil {
if frame != nil && !init {
if err := filter.AddFrame(frame, i, 0); err != nil {
log.Fatalf("%s\n", err)
}
i++
@@ -265,38 +263,22 @@ func main() {
continue
}
for _, frame := range frames {
log.Printf("frame: %dX%d", frame.Width(), frame.Height())
if err := filter.AddFrame(frame, i); err != nil {
log.Fatalf("%s\n", err)
}
ff, err := filter.GetFrame()
if err != nil {
log.Printf("err != nil - %s\n", err)
continue
}
if len(ff) == 0 {
log.Printf("len(ff) = 0\n")
filtered = nil
break
}
for idx, f := range ff {
log.Printf("ff[%d]: %dX%d\n", idx, f.Width(), f.Height())
}
filtered = append(filtered, ff...)
if err := filter.AddFrame(frame, i, 4); err != nil {
log.Fatalf("%s\n", err)
}
if len(filtered) == 0 {
log.Printf("len(filtered) == 0\n")
if ff, err = filter.GetFrame(); err != nil && len(ff) == 0 {
log.Printf("GetFrame() returned '%s', continue\n", err)
i++
continue
}
packets, err := ost.CodecCtx().Encode(filtered, -1)
if len(ff) == 0 {
i++
continue
}
packets, err := ost.CodecCtx().Encode(ff, -1)
if err != nil {
log.Fatalf("%s\n", err)
}
@@ -313,7 +295,7 @@ func main() {
}
i++
fmt.Printf("------------------------\n")
}
octx.WriteTrailer()
}

Binary file not shown.

View File

@@ -1,273 +0,0 @@
package main
import (
"errors"
"flag"
"fmt"
"log"
"strings"
"github.com/3d0c/gmf"
)
type arrayFlags []string
func (i *arrayFlags) String() string {
return strings.Join(*i, " ")
}
func (i *arrayFlags) Set(value string) error {
*i = append(*i, strings.TrimSpace(value))
return nil
}
func assert(i interface{}, err error) interface{} {
if err != nil {
log.Fatal(err)
}
return i
}
func addStream(codecName string, oc *gmf.FmtCtx, ist *gmf.Stream) (int, int) {
var cc *gmf.CodecCtx
var ost *gmf.Stream
codec := assert(gmf.FindEncoder(codecName)).(*gmf.Codec)
// Create Video stream in output context
if ost = oc.NewStream(codec); ost == nil {
log.Fatal(errors.New("unable to create stream in output context"))
}
defer gmf.Release(ost)
if cc = gmf.NewCodecCtx(codec); cc == nil {
log.Fatal(errors.New("unable to create codec context"))
}
defer gmf.Release(cc)
if oc.IsGlobalHeader() {
cc.SetFlag(gmf.CODEC_FLAG_GLOBAL_HEADER)
}
if codec.IsExperimental() {
cc.SetStrictCompliance(gmf.FF_COMPLIANCE_EXPERIMENTAL)
}
if cc.Type() == gmf.AVMEDIA_TYPE_AUDIO {
cc.SetSampleFmt(ist.CodecCtx().SampleFmt())
cc.SetSampleRate(ist.CodecCtx().SampleRate())
cc.SetChannels(ist.CodecCtx().Channels())
cc.SelectChannelLayout()
cc.SelectSampleRate()
}
if cc.Type() == gmf.AVMEDIA_TYPE_VIDEO {
cc.SetTimeBase(gmf.AVR{1, 25})
ost.SetTimeBase(gmf.AVR{1, 25})
cc.SetProfile(gmf.FF_PROFILE_MPEG4_SIMPLE)
fmt.Printf("setup dims: %d, %d\n", ist.CodecCtx().Width(), ist.CodecCtx().Height())
cc.SetDimension(ist.CodecCtx().Width(), ist.CodecCtx().Height())
cc.SetPixFmt(ist.CodecCtx().PixFmt())
}
if err := cc.Open(nil); err != nil {
log.Fatal(err)
}
ost.SetCodecCtx(cc)
return ist.Index(), ost.Index()
}
func multiplex(inputs []*gmf.FmtCtx, filter *gmf.Filter) <-chan *gmf.Frame {
var (
out chan *gmf.Frame = make(chan *gmf.Frame)
lf []*gmf.Frame = make([]*gmf.Frame, len(inputs), len(inputs))
ff []*gmf.Frame = make([]*gmf.Frame, 0)
finished map[int]bool = map[int]bool{}
pkt *gmf.Packet
ist *gmf.Stream
err error
ret int
)
go func(out chan *gmf.Frame) {
loop:
for {
for y := 0; y < len(inputs); y++ {
ictx := inputs[y]
pkt, err = ictx.GetNextPacket()
if err != nil && err.Error() != "End of file" {
log.Fatalf("error getting next packet - %s", err)
break loop
} else if err != nil && pkt == nil {
if lf == nil {
log.Fatalf("last frame is not initialized\n")
}
finished[y] = true
tmp := lf[y].CloneNewFrame()
if err := filter.AddFrame(tmp, y); err != nil {
log.Fatalf("%s\n", err)
}
ff, ret = filter.GetFrame2()
} else {
ist, err = ictx.GetStream(pkt.StreamIndex())
if err != nil {
log.Fatalf("%s\n", err)
}
if ist.IsAudio() {
continue
}
frames, err := ist.CodecCtx().Decode(pkt)
if err != nil && err.Error() != "End of file" {
log.Fatalf("error decoding - %s\n", err)
} else if err != nil {
log.Printf("error decoding pkt - %s\n", err)
}
for i, _ := range frames {
lf[y] = frames[i].CloneNewFrame()
if err := filter.AddFrame(frames[i], y); err != nil {
log.Fatalf("%s\n", err)
}
}
ff, ret = filter.GetFrame2()
}
log.Printf("ret=%d\n", ret)
if len(finished) == len(inputs) {
break loop
}
}
// for i, _ := range ff {
// out <- ff[i]
// }
}
close(out)
}(out)
return out
}
func main() {
var (
src arrayFlags
dst string
streamMap map[int]int = make(map[int]int)
)
log.SetFlags(log.Lshortfile)
flag.Var(&src, "src", "source files, e.g.: -src=1.mp4 -src=image.png")
flag.StringVar(&dst, "dst", "", "destination file, e.g. -dst=result.mp4")
flag.Parse()
if len(src) == 0 || dst == "" {
log.Fatal("at least one source and destination required")
}
octx, err := gmf.NewOutputCtx(dst)
if err != nil {
log.Fatal(err)
}
inputs := make([]*gmf.FmtCtx, 0)
for i, name := range src {
ictx, err := gmf.NewInputCtx(name)
if err != nil {
log.Fatal(err)
}
inputs = append(inputs, ictx)
log.Printf("src[%d]=%s\n", i, name)
}
srcVideoStream, err := inputs[0].GetBestStream(gmf.AVMEDIA_TYPE_VIDEO)
if err != nil {
log.Fatalf("No video stream found\n")
} else {
i, o := addStream("libx264", octx, srcVideoStream)
streamMap[i] = o
}
srcStreams := []*gmf.Stream{}
for i, _ := range inputs {
for idx := 0; idx < inputs[i].StreamsCnt(); idx++ {
stream, err := inputs[i].GetStream(idx)
if err != nil {
log.Fatalf("error getting stream - %s\n", err)
}
if !stream.IsVideo() {
continue
}
srcStreams = append(srcStreams, stream)
}
}
options := []*gmf.Option{
/*
{
Key: "pix_fmts", Val: []int32{gmf.AV_PIX_FMT_YUV420P},
},
*/
}
var (
ist *gmf.Stream
ost *gmf.Stream
)
for i, stream := range srcStreams {
fmt.Printf("srcStreams[%d] - %s, %d\n", i, stream.CodecCtx().Codec().LongName(), stream.CodecCtx().Width())
}
ost, err = octx.GetStream(0)
if err != nil {
log.Fatalf("can't get stream - %s\n", err)
}
filter, err := gmf.NewFilter("overlay=10:main_h-overlay_h-10", srcStreams, ost, options)
if err != nil {
log.Fatalf("%s\n", err)
}
if err := octx.WriteHeader(); err != nil {
log.Fatalf("error writing header - %s\n", err)
}
for frame := range multiplex(inputs, filter) {
packets, err := ost.CodecCtx().Encode([]*gmf.Frame{frame}, -1)
if err != nil {
log.Fatalf("%s\n", err)
}
for _, op := range packets {
gmf.RescaleTs(op, ist.TimeBase(), ost.TimeBase())
op.SetStreamIndex(ost.Index())
op.Dump()
if err = octx.WritePacket(op); err != nil {
break
}
op.Free()
}
}
octx.WriteTrailer()
}

179
filter.go
View File

@@ -17,6 +17,11 @@ int gmf_av_buffersrc_add_frame_flags(AVFilterContext **filt_ctx, AVFrame *frame,
return av_buffersrc_add_frame_flags(filt_ctx[i], frame, flags);
}
// XXX PTS! Pass actual pts instead of 0.
int gmf_av_buffersrc_close(AVFilterContext **filt_ctx, int i) {
return av_buffersrc_close(filt_ctx[i], 0, AV_BUFFERSRC_FLAG_PUSH);
}
AVFilterContext *gmf_get_current(AVFilterContext **filt_ctx, int i) {
return filt_ctx[i];
}
@@ -31,19 +36,22 @@ import (
)
const (
AV_BUFFERSINK_FLAG_PEEK = 1
AV_BUFFERSINK_FLAG_NO_REQUEST = 2
AV_BUFFERSRC_FLAG_PUSH = 4
)
type Filter struct {
bufferCtx **_Ctype_AVFilterContext
bufferCtx []*_Ctype_AVFilterContext
sinkCtx *_Ctype_AVFilterContext
filterGraph *_Ctype_AVFilterGraph
bufferCtxNb int
}
func NewFilter(desc string, srcStreams []*Stream, ost *Stream, options []*Option) (*Filter, error) {
f := &Filter{}
f := &Filter{
filterGraph: C.avfilter_graph_alloc(),
bufferCtx: make([]*_Ctype_AVFilterContext, 0),
}
var (
ret, i int
@@ -52,19 +60,8 @@ func NewFilter(desc string, srcStreams []*Stream, ost *Stream, options []*Option
outputs *_Ctype_AVFilterInOut
curr *_Ctype_AVFilterInOut
last *_Ctype_AVFilterContext
format *_Ctype_AVFilterContext
)
cnameOut := C.CString("out")
defer C.free(unsafe.Pointer(cnameOut))
f.filterGraph = C.avfilter_graph_alloc()
f.bufferCtxNb = len(srcStreams)
csz := C.ulong(f.bufferCtxNb)
f.bufferCtx = (**_Ctype_struct_AVFilterContext)(C.av_calloc(csz, C.sizeof_AVFilterContext))
cdesc := C.CString(desc)
defer C.free(unsafe.Pointer(cdesc))
@@ -74,97 +71,93 @@ func NewFilter(desc string, srcStreams []*Stream, ost *Stream, options []*Option
&inputs,
&outputs,
)); ret < 0 {
return nil, fmt.Errorf("error parsing filter graph - %s", AvError(ret))
return f, fmt.Errorf("error parsing filter graph - %s", AvError(ret))
}
defer C.avfilter_inout_free(&inputs)
defer C.avfilter_inout_free(&outputs)
for curr = inputs; curr != nil; curr = curr.next {
if len(srcStreams) < i {
return nil, fmt.Errorf("not enough of source streams")
}
srcStream := srcStreams[i]
args = fmt.Sprintf("video_size=%s:pix_fmt=%d:time_base=%s:pixel_aspect=%s:sws_param=flags=%d", srcStream.CodecCtx().GetVideoSize(), srcStream.CodecCtx().PixFmt(), srcStream.TimeBase().AVR(), srcStream.CodecCtx().GetAspectRation().AVR(), SWS_BILINEAR)
src := srcStreams[i]
cargs := C.CString(args)
args = fmt.Sprintf("video_size=%s:pix_fmt=%d:time_base=%s:pixel_aspect=%s:sws_param=flags=%d:frame_rate=%s", src.CodecCtx().GetVideoSize(), src.CodecCtx().PixFmt(), src.TimeBase().AVR(), src.CodecCtx().GetAspectRation().AVR(), SWS_BILINEAR, src.GetRFrameRate().AVR().String())
ci := C.int(i)
name := fmt.Sprintf("in_%d", i)
cname := C.CString(name)
fmt.Printf("args: %s\n", args)
if ret = int(C.gmf_create_filter(
f.bufferCtx,
C.avfilter_get_by_name(C.CString("buffer")),
cname,
cargs,
nil,
f.filterGraph,
ci)); ret < 0 {
return nil, fmt.Errorf("error creating filter 'buffer' - %s", AvError(ret))
if last, ret = f.create("buffer", fmt.Sprintf("in_%d", i), args); ret < 0 {
return f, fmt.Errorf("error creating input buffer - %s", AvError(ret))
}
last = C.gmf_get_current(f.bufferCtx, ci)
f.bufferCtx = append(f.bufferCtx, last)
if ret = int(C.avfilter_link(last, 0, curr.filter_ctx, C.uint(i))); ret < 0 {
return nil, fmt.Errorf("error linking filters - %s", AvError(ret))
return f, fmt.Errorf("error linking filters - %s", AvError(ret))
}
i++
}
if ret = int(C.avfilter_graph_create_filter(
&f.sinkCtx,
C.avfilter_get_by_name(C.CString("buffersink")),
cnameOut,
nil,
nil,
f.filterGraph)); ret < 0 {
return nil, fmt.Errorf("error creating filter 'buffersink' - %s", AvError(ret))
if f.sinkCtx, ret = f.create("buffersink", "out", ""); ret < 0 {
return f, fmt.Errorf("error creating filter 'buffersink' - %s", AvError(ret))
}
// XXX PIXFMT!
if ret = int(C.avfilter_graph_create_filter(
&format,
C.avfilter_get_by_name(C.CString("format")),
C.CString("format"),
C.CString("yuv420p"),
nil,
f.filterGraph)); ret < 0 {
return nil, fmt.Errorf("error creating filter 'buffer' - %s", AvError(ret))
// XXX hardcoded PIXFMT!
if last, ret = f.create("format", "format", "yuv420p"); ret < 0 {
return f, fmt.Errorf("error creating format filter - %s", AvError(ret))
}
if ret = int(C.avfilter_link(outputs.filter_ctx, 0, format, 0)); ret < 0 {
return nil, fmt.Errorf("error linking output filters - %s", AvError(ret))
if ret = int(C.avfilter_link(outputs.filter_ctx, 0, last, 0)); ret < 0 {
return f, fmt.Errorf("error linking output filters - %s", AvError(ret))
}
if ret = int(C.avfilter_link(format, 0, f.sinkCtx, 0)); ret < 0 {
return nil, fmt.Errorf("error linking output filters - %s", AvError(ret))
if ret = int(C.avfilter_link(last, 0, f.sinkCtx, 0)); ret < 0 {
return f, fmt.Errorf("error linking output filters - %s", AvError(ret))
}
if ret = int(C.avfilter_graph_config(f.filterGraph, nil)); ret < 0 {
return nil, fmt.Errorf("graph config error - %s", AvError(ret))
return f, fmt.Errorf("graph config error - %s", AvError(ret))
}
fmt.Printf("%s\n", C.GoString(C.avfilter_graph_dump(f.filterGraph, nil)))
C.avfilter_inout_free(&inputs)
C.avfilter_inout_free(&outputs)
return f, nil
}
func (f *Filter) AddFrame(frame *Frame, istIdx int) error {
func (f *Filter) create(filter, name, args string) (*_Ctype_AVFilterContext, int) {
var (
ctx *_Ctype_AVFilterContext
ret int
)
cfilter := C.CString(filter)
cname := C.CString(name)
cargs := C.CString(args)
ret = int(C.avfilter_graph_create_filter(
&ctx,
C.avfilter_get_by_name(cfilter),
cname,
cargs,
nil,
f.filterGraph))
C.free(unsafe.Pointer(cfilter))
C.free(unsafe.Pointer(cname))
C.free(unsafe.Pointer(cargs))
return ctx, ret
}
func (f *Filter) AddFrame(frame *Frame, istIdx int, flag int) error {
var ret int
fmt.Printf("AddFrame: i=%d, width=%d, height=%d\n", istIdx, frame.Width(), frame.Height())
if istIdx >= len(f.bufferCtx) {
return fmt.Errorf("unexpected stream index #%d", istIdx)
}
if ret = int(C.gmf_av_buffersrc_add_frame_flags(
f.bufferCtx,
if ret = int(C.av_buffersrc_add_frame_flags(
f.bufferCtx[istIdx],
frame.avFrame,
AV_BUFFERSRC_FLAG_PUSH,
C.int(istIdx)),
C.int(flag)),
); ret < 0 {
return AvError(ret)
}
@@ -172,6 +165,16 @@ func (f *Filter) AddFrame(frame *Frame, istIdx int) error {
return nil
}
func (f *Filter) Close(istIdx int) error {
var ret int
if ret = int(C.av_buffersrc_close(f.bufferCtx[istIdx], 0, AV_BUFFERSRC_FLAG_PUSH)); ret < 0 {
return AvError(ret)
}
return nil
}
func (f *Filter) GetFrame() ([]*Frame, error) {
var (
ret int
@@ -182,7 +185,6 @@ func (f *Filter) GetFrame() ([]*Frame, error) {
frame := NewFrame()
ret = int(C.av_buffersink_get_frame_flags(f.sinkCtx, frame.avFrame, AV_BUFFERSINK_FLAG_NO_REQUEST))
fmt.Printf("ret=%d\n", ret)
if AvErrno(ret) == syscall.EAGAIN || ret == AVERROR_EOF {
frame.Free()
break
@@ -194,26 +196,9 @@ func (f *Filter) GetFrame() ([]*Frame, error) {
result = append(result, frame)
}
return result, nil
}
f.RequestOldest()
func (f *Filter) GetFrame2() ([]*Frame, int) {
var (
ret int
result []*Frame = make([]*Frame, 0)
)
for {
frame := NewFrame()
if ret = int(C.av_buffersink_get_frame_flags(f.sinkCtx, frame.avFrame, AV_BUFFERSINK_FLAG_NO_REQUEST)); ret < 0 {
return nil, ret
}
result = append(result, frame)
}
return result, ret
return result, AvError(ret)
}
func (f *Filter) RequestOldest() error {
@@ -225,3 +210,19 @@ func (f *Filter) RequestOldest() error {
return nil
}
func (f *Filter) Dump() {
fmt.Println(C.GoString(C.avfilter_graph_dump(f.filterGraph, nil)))
}
func (f *Filter) Release() {
if f.sinkCtx != nil {
C.avfilter_free(f.sinkCtx)
}
for i, _ := range f.bufferCtx {
C.avfilter_free(f.bufferCtx[i])
}
C.avfilter_graph_free(&f.filterGraph)
}