mirror of
https://github.com/3d0c/gmf
synced 2025-12-24 10:40:59 +08:00
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
*.mpg
|
||||
*.exe
|
||||
!examples/tests-sample.mp4
|
||||
!examples/bbb.mp4
|
||||
remuxing
|
||||
transcode
|
||||
*.ts
|
||||
|
||||
29
codecCtx.go
29
codecCtx.go
@@ -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
BIN
examples/bbb.mp4
Normal file
Binary file not shown.
BIN
examples/test.png
Normal file
BIN
examples/test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
301
examples/watermark.go
Normal file
301
examples/watermark.go
Normal 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
211
filter.go
Normal 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)
|
||||
}
|
||||
4
frame.go
4
frame.go
@@ -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)}
|
||||
}
|
||||
|
||||
@@ -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
2
sws.go
@@ -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,
|
||||
|
||||
6
utils.go
6
utils.go
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user