Merge pull request #85 from 3d0c/feature/avfilter

Feature/avfilter
This commit is contained in:
alex
2019-01-07 17:39:47 +03:00
committed by GitHub
10 changed files with 555 additions and 2 deletions

1
.gitignore vendored
View File

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

View File

@@ -516,6 +516,10 @@ func (cc *CodecCtx) GetVideoSize() string {
return fmt.Sprintf("%dx%d", cc.Width(), cc.Height())
}
func (cc *CodecCtx) GetAspectRation() AVRational {
return AVRational(cc.avCodecCtx.sample_aspect_ratio)
}
func (cc *CodecCtx) Decode(pkt *Packet) ([]*Frame, error) {
var (
ret int
@@ -543,8 +547,6 @@ func (cc *CodecCtx) Decode(pkt *Packet) ([]*Frame, error) {
return nil, AvError(ret)
}
frame.SetPictType()
result = append(result, frame)
}
@@ -588,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.

BIN
examples/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

301
examples/watermark.go Normal file
View File

@@ -0,0 +1,301 @@
package main
import (
"errors"
"flag"
"log"
"strings"
"syscall"
"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
}
type Input struct {
ctx *gmf.FmtCtx
lastFrame *gmf.Frame
finished bool
}
var (
inputs map[int]*Input
)
func finishedNb() int {
result := 0
for _, input := range inputs {
if input.finished {
result++
}
}
return result
}
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)
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 {
log.Fatal(err)
}
ost.SetCodecCtx(cc)
return ist.Index(), ost.Index()
}
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, e.g.\n./watermark -src=bbb.mp4 -src=test.png -dst=overlay.mp4")
}
octx, err := gmf.NewOutputCtx(dst)
if err != nil {
log.Fatal(err)
}
inputs = make(map[int]*Input)
for i, name := range src {
ictx, err := gmf.NewInputCtx(name)
if err != nil {
log.Fatal(err)
}
inputs[i] = &Input{}
inputs[i].ctx = ictx
log.Printf("Source #%d - %s\n", i, name)
}
srcVideoStream, err := inputs[0].ctx.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].ctx.StreamsCnt(); idx++ {
stream, err := inputs[i].ctx.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 (
i, ret int = 0, 0
pkt *gmf.Packet
ist *gmf.Stream
ost *gmf.Stream
)
for i, stream := range srcStreams {
log.Printf("stream #%d - %s, %s\n", i, stream.CodecCtx().Codec().LongName(), stream.CodecCtx().GetVideoSize())
}
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)
}
if err := octx.WriteHeader(); err != nil {
log.Fatalf("error writing header - %s\n", err)
}
init := false
var (
frame *gmf.Frame
ff []*gmf.Frame
)
for {
if finishedNb() == len(inputs) {
log.Printf("Finished all\n")
break
}
if i == len(inputs) {
i = 0
}
if inputs[i].finished {
i++
continue
}
ictx := inputs[i].ctx
pkt, err = ictx.GetNextPacket()
if err != nil && err.Error() != "End of file" {
log.Fatalf("error getting next packet - %s", err)
} else if err != nil && pkt == nil {
if !inputs[i].finished {
log.Printf("EOF input #%d, closing\n", i)
filter.RequestOldest()
filter.Close(i)
inputs[i].finished = true
}
i++
continue
}
ist, err = ictx.GetStream(pkt.StreamIndex())
if err != nil {
log.Fatalf("%s\n", err)
}
if ist.IsAudio() {
continue
}
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))
}
if frame != nil && !init {
if err := filter.AddFrame(frame, i, 0); err != nil {
log.Fatalf("%s\n", err)
}
i++
init = true
continue
}
if err := filter.AddFrame(frame, i, 4); err != nil {
log.Fatalf("%s\n", err)
}
if ff, err = filter.GetFrame(); err != nil && len(ff) == 0 {
log.Printf("GetFrame() returned '%s', continue\n", err)
i++
continue
}
if len(ff) == 0 {
i++
continue
}
packets, err := ost.CodecCtx().Encode(ff, -1)
if err != nil {
log.Fatalf("%s\n", err)
}
for _, op := range packets {
gmf.RescaleTs(op, ist.TimeBase(), ost.TimeBase())
op.SetStreamIndex(ost.Index())
if err = octx.WritePacket(op); err != nil {
break
}
op.Free()
}
i++
}
octx.WriteTrailer()
}

211
filter.go Normal file
View File

@@ -0,0 +1,211 @@
package gmf
/*
#cgo pkg-config: libavfilter
#include <stdio.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/pixdesc.h>
*/
import "C"
import (
"fmt"
"syscall"
"unsafe"
)
const (
AV_BUFFERSINK_FLAG_PEEK = 1
AV_BUFFERSINK_FLAG_NO_REQUEST = 2
AV_BUFFERSRC_FLAG_PUSH = 4
)
type Filter struct {
bufferCtx []*_Ctype_AVFilterContext
sinkCtx *_Ctype_AVFilterContext
filterGraph *_Ctype_AVFilterGraph
}
func NewFilter(desc string, srcStreams []*Stream, ost *Stream, options []*Option) (*Filter, error) {
f := &Filter{
filterGraph: C.avfilter_graph_alloc(),
bufferCtx: make([]*_Ctype_AVFilterContext, 0),
}
var (
ret, i int
args string
inputs *_Ctype_AVFilterInOut
outputs *_Ctype_AVFilterInOut
curr *_Ctype_AVFilterInOut
last *_Ctype_AVFilterContext
)
cdesc := C.CString(desc)
defer C.free(unsafe.Pointer(cdesc))
if ret = int(C.avfilter_graph_parse2(
f.filterGraph,
cdesc,
&inputs,
&outputs,
)); ret < 0 {
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")
}
src := srcStreams[i]
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())
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))
}
f.bufferCtx = append(f.bufferCtx, last)
if ret = int(C.avfilter_link(last, 0, curr.filter_ctx, C.uint(i))); ret < 0 {
return f, fmt.Errorf("error linking filters - %s", AvError(ret))
}
i++
}
if f.sinkCtx, ret = f.create("buffersink", "out", ""); ret < 0 {
return f, fmt.Errorf("error creating filter 'buffersink' - %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, last, 0)); ret < 0 {
return f, 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 f, fmt.Errorf("graph config error - %s", AvError(ret))
}
return f, nil
}
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
if istIdx >= len(f.bufferCtx) {
return fmt.Errorf("unexpected stream index #%d", istIdx)
}
if ret = int(C.av_buffersrc_add_frame_flags(
f.bufferCtx[istIdx],
frame.avFrame,
C.int(flag)),
); ret < 0 {
return AvError(ret)
}
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
result []*Frame = make([]*Frame, 0)
)
for {
frame := NewFrame()
ret = int(C.av_buffersink_get_frame_flags(f.sinkCtx, frame.avFrame, AV_BUFFERSINK_FLAG_NO_REQUEST))
if AvErrno(ret) == syscall.EAGAIN || ret == AVERROR_EOF {
frame.Free()
break
} else if ret < 0 {
frame.Free()
return nil, AvError(ret)
}
result = append(result, frame)
}
f.RequestOldest()
return result, AvError(ret)
}
func (f *Filter) RequestOldest() error {
var ret int
if ret = int(C.avfilter_graph_request_oldest(f.filterGraph)); ret < 0 {
return AvError(ret)
}
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)
}

View File

@@ -182,6 +182,10 @@ func (f *Frame) LineSize(idx int) int {
return int(C.gmf_get_frame_line_size(f.avFrame, C.int(idx)))
}
func (f *Frame) Dump() {
fmt.Printf("%v\n", f.avFrame)
}
func (f *Frame) CloneNewFrame() *Frame {
return &Frame{avFrame: C.av_frame_clone(f.avFrame)}
}

View File

@@ -31,6 +31,9 @@ func (this Option) Set(ctx interface{}) {
case int:
ret = int(C.av_opt_set_int(unsafe.Pointer(reflect.ValueOf(ctx).Pointer()), ckey, C.int64_t(this.Val.(int)), C.AV_OPT_SEARCH_CHILDREN))
case []int32:
ret = int(C.av_opt_set_bin(unsafe.Pointer(reflect.ValueOf(ctx).Pointer()), ckey, (*C.uchar)(unsafe.Pointer(&this.Val.([]int)[0])), C.int(this.Val.([]int)[0]), C.AV_OPT_SEARCH_CHILDREN))
case int32:
ret = int(C.av_opt_set_int(unsafe.Pointer(reflect.ValueOf(ctx).Pointer()), ckey, C.int64_t(this.Val.(int32)), C.AV_OPT_SEARCH_CHILDREN))

2
sws.go
View File

@@ -59,9 +59,11 @@ func NewPicSwsCtx(srcWidth int, srcHeight int, srcPixFmt int32, dst *CodecCtx, m
return &SwsCtx{swsCtx: ctx}
}
func (this *SwsCtx) Free() {
C.sws_freeContext(this.swsCtx)
}
func (this *SwsCtx) Scale(src *Frame, dst *Frame) {
C.sws_scale(
this.swsCtx,

View File

@@ -9,11 +9,17 @@ package gmf
#include "libavutil/rational.h"
#include "libavutil/samplefmt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
uint32_t return_int (int num) {
return (uint32_t)(num);
}
uint8_t * gmf_alloc_buffer(int32_t fmt, int width, int height) {
int numBytes = av_image_get_buffer_size(fmt, width, height, 0);
return (uint8_t *) av_malloc(numBytes*sizeof(uint8_t));
}
*/
import "C"