diff --git a/avio.go b/avio_go111.go similarity index 99% rename from avio.go rename to avio_go111.go index 4686a3b..bd2bc38 100644 --- a/avio.go +++ b/avio_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + package gmf /* diff --git a/avio_go112.go b/avio_go112.go new file mode 100644 index 0000000..5789446 --- /dev/null +++ b/avio_go112.go @@ -0,0 +1,180 @@ +// +build go1.12 + +package gmf + +/* + +#cgo pkg-config: libavformat + +#include +#include +#include +#include + +#include "libavformat/avio.h" +#include "libavformat/avformat.h" + +extern int readCallBack(void*, uint8_t*, int); +extern int writeCallBack(void*, uint8_t*, int); +extern int64_t seekCallBack(void*, int64_t, int); + +*/ +import "C" + +import ( + "errors" + "fmt" + "unsafe" +) + +const ( + AVIO_FLAG_READ = 1 + AVIO_FLAG_WRITE = 2 + AVIO_FLAG_READ_WRITE = (AVIO_FLAG_READ | AVIO_FLAG_WRITE) +) + +var ( + IO_BUFFER_SIZE int = 32768 +) + +// Functions prototypes for custom IO. Implement necessary prototypes and pass instance pointer to NewAVIOContext. +// +// E.g.: +// func gridFsReader() ([]byte, int) { +// ... implementation ... +// return data, length +// } +// +// avoictx := NewAVIOContext(ctx, &AVIOHandlers{ReadPacket: gridFsReader}) +type AVIOHandlers struct { + ReadPacket func() ([]byte, int) + WritePacket func([]byte) int + Seek func(int64, int) int64 +} + +// Global map of AVIOHandlers +// one handlers struct per format context. Using ctx.avCtx pointer address as a key. +var handlersMap map[uintptr]*AVIOHandlers + +type AVIOContext struct { + avAVIOContext *C.AVIOContext + // avAVIOContext *C.struct_AVIOContext + handlerKey uintptr + CgoMemoryManage + buffer *C.uchar +} + +// AVIOContext constructor. Use it only if You need custom IO behaviour! +func NewAVIOContext(ctx *FmtCtx, handlers *AVIOHandlers, size ...int) (*AVIOContext, error) { + this := &AVIOContext{} + + bufferSize := IO_BUFFER_SIZE + + if len(size) == 1 { + bufferSize = size[0] + } + + this.buffer = (*C.uchar)(C.av_malloc(C.size_t(bufferSize))) + + if this.buffer == nil { + return nil, errors.New("unable to allocate buffer") + } + + // we have to explicitly set it to nil, to force library using default handlers + var ptrRead, ptrWrite, ptrSeek *[0]byte = nil, nil, nil + + if handlers != nil { + if handlersMap == nil { + handlersMap = make(map[uintptr]*AVIOHandlers) + } + + handlersMap[uintptr(unsafe.Pointer(ctx.avCtx))] = handlers + this.handlerKey = uintptr(unsafe.Pointer(ctx.avCtx)) + } + + var flag int = 0 + + if handlers.ReadPacket != nil { + ptrRead = (*[0]byte)(C.readCallBack) + flag = 0 + } + + if handlers.WritePacket != nil { + ptrWrite = (*[0]byte)(C.writeCallBack) + flag = AVIO_FLAG_WRITE + } + + if handlers.Seek != nil { + ptrSeek = (*[0]byte)(C.seekCallBack) + } + + if handlers.ReadPacket != nil && handlers.WritePacket != nil { + flag = AVIO_FLAG_READ_WRITE + } + + if this.avAVIOContext = C.avio_alloc_context(this.buffer, C.int(bufferSize), C.int(flag), unsafe.Pointer(ctx.avCtx), ptrRead, ptrWrite, ptrSeek); this.avAVIOContext == nil { + C.av_free(unsafe.Pointer(this.avAVIOContext.buffer)) + return nil, errors.New("unable to initialize avio context") + } + + this.avAVIOContext.min_packet_size = C.int(bufferSize) + + return this, nil +} + +func (this *AVIOContext) Free() { + delete(handlersMap, this.handlerKey) + C.av_free(unsafe.Pointer(this.avAVIOContext.buffer)) + C.av_free(unsafe.Pointer(this.avAVIOContext)) +} + +func (this *AVIOContext) Flush() { + C.avio_flush(this.avAVIOContext) +} + +//export readCallBack +func readCallBack(opaque unsafe.Pointer, buf *C.uint8_t, buf_size C.int) C.int { + handlers, found := handlersMap[uintptr(opaque)] + if !found { + panic(fmt.Sprintf("No handlers instance found, according pointer: %v", opaque)) + } + + if handlers.ReadPacket == nil { + panic("No reader handler initialized") + } + + b, n := handlers.ReadPacket() + if n > 0 { + C.memcpy(unsafe.Pointer(buf), unsafe.Pointer(&b[0]), C.size_t(n)) + } + + return C.int(n) +} + +//export writeCallBack +func writeCallBack(opaque unsafe.Pointer, buf *C.uint8_t, buf_size C.int) C.int { + handlers, found := handlersMap[uintptr(opaque)] + if !found { + panic(fmt.Sprintf("No handlers instance found, according pointer: %v", opaque)) + } + + if handlers.WritePacket == nil { + panic("No writer handler initialized.") + } + + return C.int(handlers.WritePacket(C.GoBytes(unsafe.Pointer(buf), buf_size))) +} + +//export seekCallBack +func seekCallBack(opaque unsafe.Pointer, offset C.int64_t, whence C.int) C.int64_t { + handlers, found := handlersMap[uintptr(opaque)] + if !found { + panic(fmt.Sprintf("No handlers instance found, according pointer: %v", opaque)) + } + + if handlers.Seek == nil { + panic("No seek handler initialized.") + } + + return C.int64_t(handlers.Seek(int64(offset), int(whence))) +} diff --git a/codecCtx.go b/codecCtx_go111.go similarity index 98% rename from codecCtx.go rename to codecCtx_go111.go index 798a134..f85dfa6 100644 --- a/codecCtx.go +++ b/codecCtx_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + package gmf /* @@ -193,7 +195,7 @@ func (cc *CodecCtx) CopyExtra(ist *Stream) *CodecCtx { codec.field_order = icodec.field_order - codec.extradata = (*_Ctype_uint8_t)(C.av_mallocz((_Ctype_size_t)((C.uint64_t)(icodec.extradata_size) + C.AV_INPUT_BUFFER_PADDING_SIZE))) + codec.extradata = (*Ctype_uint8_t)(C.av_mallocz((_Ctype_size_t)((C.uint64_t)(icodec.extradata_size) + C.AV_INPUT_BUFFER_PADDING_SIZE))) C.memcpy(unsafe.Pointer(codec.extradata), unsafe.Pointer(icodec.extradata), (_Ctype_size_t)(icodec.extradata_size)) codec.extradata_size = icodec.extradata_size diff --git a/codecCtx_go112.go b/codecCtx_go112.go new file mode 100644 index 0000000..147f5db --- /dev/null +++ b/codecCtx_go112.go @@ -0,0 +1,608 @@ +// +build go1.12 + +package gmf + +/* + +#cgo pkg-config: libavcodec libavutil + +#include +#include + +#include "libavcodec/avcodec.h" +#include "libavutil/channel_layout.h" +#include "libavutil/samplefmt.h" +#include "libavutil/opt.h" +#include "libavutil/mem.h" +#include "libavutil/bprint.h" + +static int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) { + const enum AVSampleFormat *p = codec->sample_fmts; + + while (*p != AV_SAMPLE_FMT_NONE) { + if (*p == sample_fmt) + return 1; + p++; + } + return 0; +} + +static int select_sample_rate(AVCodec *codec) { + const int *p; + int best_samplerate = 0; + + if (!codec->supported_samplerates) + return 44100; + + p = codec->supported_samplerates; + while (*p) { + best_samplerate = FFMAX(*p, best_samplerate); + p++; + } + return best_samplerate; +} + +static int select_channel_layout(AVCodec *codec) { + const uint64_t *p; + uint64_t best_ch_layout = 0; + int best_nb_channels = 0; + + if (!codec->channel_layouts) + return AV_CH_LAYOUT_STEREO; + + p = codec->channel_layouts; + while (*p) { + int nb_channels = av_get_channel_layout_nb_channels(*p); + + if (nb_channels > best_nb_channels) { + best_ch_layout = *p; + best_nb_channels = nb_channels; + } + p++; + } + return best_ch_layout; +} + +static void call_av_freep(AVCodecContext *out){ + return av_freep(&out); +} + +static char * gmf_get_channel_layout_name(int channels, int layout) { + AVBPrint pbuf; + + av_bprint_init(&pbuf, 0, 1); + av_bprint_channel_layout(&pbuf, channels, layout); + + char *result = av_mallocz(pbuf.len); + + memcpy(result, pbuf.str, pbuf.len); + + av_bprint_clear(&pbuf); + + return result; +} + +*/ +import "C" + +import ( + "errors" + "fmt" + "syscall" + "unsafe" +) + +const ( + AVCOL_RANGE_UNSPECIFIED = iota + AVCOL_RANGE_MPEG ///< the normal 219*2^(n-8) "MPEG" YUV ranges + AVCOL_RANGE_JPEG ///< the normal 2^n-1 "JPEG" YUV ranges + AVCOL_RANGE_NB ///< Not part of ABI +) + +var ( + AV_CODEC_ID_MPEG1VIDEO int = C.AV_CODEC_ID_MPEG1VIDEO + AV_CODEC_ID_MPEG2VIDEO int = C.AV_CODEC_ID_MPEG2VIDEO + AV_CODEC_ID_H264 int = C.AV_CODEC_ID_H264 + AV_CODEC_ID_MPEG4 int = C.AV_CODEC_ID_MPEG4 + AV_CODEC_ID_JPEG2000 int = C.AV_CODEC_ID_JPEG2000 + AV_CODEC_ID_MJPEG int = C.AV_CODEC_ID_MJPEG + AV_CODEC_ID_MSMPEG4V1 int = C.AV_CODEC_ID_MSMPEG4V1 + AV_CODEC_ID_MSMPEG4V2 int = C.AV_CODEC_ID_MSMPEG4V2 + AV_CODEC_ID_MSMPEG4V3 int = C.AV_CODEC_ID_MSMPEG4V3 + AV_CODEC_ID_WMV1 int = C.AV_CODEC_ID_WMV1 + AV_CODEC_ID_WMV2 int = C.AV_CODEC_ID_WMV2 + AV_CODEC_ID_FLV1 int = C.AV_CODEC_ID_FLV1 + AV_CODEC_ID_PNG int = C.AV_CODEC_ID_PNG + AV_CODEC_ID_TIFF int = C.AV_CODEC_ID_TIFF + AV_CODEC_ID_GIF int = C.AV_CODEC_ID_GIF + AV_CODEC_ID_RAWVIDEO int = C.AV_CODEC_ID_RAWVIDEO + + CODEC_FLAG_GLOBAL_HEADER int = C.AV_CODEC_FLAG_GLOBAL_HEADER + FF_MB_DECISION_SIMPLE int = C.FF_MB_DECISION_SIMPLE + FF_MB_DECISION_BITS int = C.FF_MB_DECISION_BITS + FF_MB_DECISION_RD int = C.FF_MB_DECISION_RD + + AV_SAMPLE_FMT_U8 int32 = C.AV_SAMPLE_FMT_U8 + AV_SAMPLE_FMT_S16 int32 = C.AV_SAMPLE_FMT_S16 + AV_SAMPLE_FMT_S32 int32 = C.AV_SAMPLE_FMT_S32 + AV_SAMPLE_FMT_FLT int32 = C.AV_SAMPLE_FMT_FLT + AV_SAMPLE_FMT_DBL int32 = C.AV_SAMPLE_FMT_DBL + + AV_SAMPLE_FMT_U8P int32 = C.AV_SAMPLE_FMT_U8P + AV_SAMPLE_FMT_S16P int32 = C.AV_SAMPLE_FMT_S16P + AV_SAMPLE_FMT_S32P int32 = C.AV_SAMPLE_FMT_S32P + AV_SAMPLE_FMT_FLTP int32 = C.AV_SAMPLE_FMT_FLTP + AV_SAMPLE_FMT_DBLP int32 = C.AV_SAMPLE_FMT_DBLP + + color_range_names map[uint32]string = map[uint32]string{ + AVCOL_RANGE_UNSPECIFIED: "unknown", + AVCOL_RANGE_MPEG: "tv", + AVCOL_RANGE_JPEG: "pc", + } +) + +type avBprint C.struct_AVBprint + +type CodecCtx struct { + codec *Codec + avCodecCtx *C.struct_AVCodecContext + CgoMemoryManage +} + +func NewCodecCtx(codec *Codec, options ...[]*Option) *CodecCtx { + result := &CodecCtx{codec: codec} + + codecctx := C.avcodec_alloc_context3(codec.avCodec) + if codecctx == nil { + return nil + } + + result.avCodecCtx = codecctx + + // we're really expecting only one options-array — + // variadic arg is used for backward compatibility + if len(options) == 1 { + for _, option := range options[0] { + option.Set(result.avCodecCtx) + } + } + + result.avCodecCtx.codec_id = codec.avCodec.id + + return result +} + +func (cc *CodecCtx) SetOptions(options []Option) { + for _, option := range options { + option.Set(cc.avCodecCtx) + } +} + +func (cc *CodecCtx) CopyExtra(ist *Stream) *CodecCtx { + codec := cc.avCodecCtx + icodec := ist.CodecCtx().avCodecCtx + + codec.bits_per_raw_sample = icodec.bits_per_raw_sample + codec.chroma_sample_location = icodec.chroma_sample_location + + codec.codec_id = icodec.codec_id + codec.codec_type = icodec.codec_type + + // codec.codec_tag = icodec.codec_tag + + codec.rc_max_rate = icodec.rc_max_rate + codec.rc_buffer_size = icodec.rc_buffer_size + + codec.field_order = icodec.field_order + + codec.extradata = (*C.uint8_t)(C.av_mallocz((C.size_t)((C.uint64_t)(icodec.extradata_size) + C.AV_INPUT_BUFFER_PADDING_SIZE))) + + C.memcpy(unsafe.Pointer(codec.extradata), unsafe.Pointer(icodec.extradata), (C.size_t)(icodec.extradata_size)) + codec.extradata_size = icodec.extradata_size + codec.bits_per_coded_sample = icodec.bits_per_coded_sample + + codec.has_b_frames = icodec.has_b_frames + + return cc +} + +func (cc *CodecCtx) Open(dict *Dict) error { + if cc.IsOpen() { + return nil + } + + var avDict *C.struct_AVDictionary + if dict != nil { + avDict = dict.avDict + } + + if averr := C.avcodec_open2(cc.avCodecCtx, cc.codec.avCodec, &avDict); averr < 0 { + return errors.New(fmt.Sprintf("Error opening codec '%s:%s', averror: %s", cc.codec.Name(), cc.codec.LongName(), AvError(int(averr)))) + } + + return nil +} + +// codec context is freed by avformat_free_context() +func (cc *CodecCtx) Free() { + C.avcodec_free_context(&cc.avCodecCtx) + cc.codec.Free() +} + +func (cc *CodecCtx) CloseAndRelease() { + panic("(CodecCtx)CloseAndRelease() is deprecated") +} + +func (cc *CodecCtx) Close() { + C.avcodec_close(cc.avCodecCtx) +} + +// @todo +func (cc *CodecCtx) SetOpt() { + // mock + C.av_opt_set_int(unsafe.Pointer(cc.avCodecCtx), C.CString("refcounted_frames"), 1, 0) +} + +func (cc *CodecCtx) Codec() *Codec { + return &Codec{avCodec: cc.avCodecCtx.codec} +} + +func (cc *CodecCtx) Id() int { + return int(cc.avCodecCtx.codec_id) +} + +func (cc *CodecCtx) Type() int32 { + return int32(cc.avCodecCtx.codec_type) +} + +func (cc *CodecCtx) Width() int { + return int(cc.avCodecCtx.width) +} + +func (cc *CodecCtx) Height() int { + return int(cc.avCodecCtx.height) +} + +func (cc *CodecCtx) PixFmt() int32 { + return int32(cc.avCodecCtx.pix_fmt) +} + +func (cc *CodecCtx) FrameSize() int { + return int(cc.avCodecCtx.frame_size) +} + +func (cc *CodecCtx) SampleFmt() int32 { + return cc.avCodecCtx.sample_fmt +} + +func (cc *CodecCtx) SampleRate() int { + return int(cc.avCodecCtx.sample_rate) +} + +func (cc *CodecCtx) Profile() int { + return int(cc.avCodecCtx.profile) +} + +func (cc *CodecCtx) IsOpen() bool { + return (int(C.avcodec_is_open(cc.avCodecCtx)) > 0) +} + +func (cc *CodecCtx) SetProfile(profile int) *CodecCtx { + cc.avCodecCtx.profile = C.int(profile) + return cc +} + +func (cc *CodecCtx) TimeBase() AVRational { + return AVRational(cc.avCodecCtx.time_base) +} + +func (cc *CodecCtx) ChannelLayout() int { + return int(cc.avCodecCtx.channel_layout) +} +func (cc *CodecCtx) SetChannelLayout(channelLayout int) { + cc.avCodecCtx.channel_layout = C.uint64_t(channelLayout) +} + +func (cc *CodecCtx) BitRate() int { + return int(cc.avCodecCtx.bit_rate) +} + +func (cc *CodecCtx) Channels() int { + return int(cc.avCodecCtx.channels) +} + +func (cc *CodecCtx) SetBitRate(val int) *CodecCtx { + cc.avCodecCtx.bit_rate = C.int64_t(val) + return cc +} + +func (cc *CodecCtx) SetWidth(val int) *CodecCtx { + cc.avCodecCtx.width = C.int(val) + return cc +} + +func (cc *CodecCtx) SetHeight(val int) *CodecCtx { + cc.avCodecCtx.height = C.int(val) + return cc +} + +func (cc *CodecCtx) SetDimension(w, h int) *CodecCtx { + cc.avCodecCtx.width = C.int(w) + cc.avCodecCtx.height = C.int(h) + return cc +} + +func (cc *CodecCtx) SetTimeBase(val AVR) *CodecCtx { + cc.avCodecCtx.time_base.num = C.int(val.Num) + cc.avCodecCtx.time_base.den = C.int(val.Den) + return cc +} + +func (cc *CodecCtx) SetGopSize(val int) *CodecCtx { + cc.avCodecCtx.gop_size = C.int(val) + return cc +} + +func (cc *CodecCtx) SetMaxBFrames(val int) *CodecCtx { + cc.avCodecCtx.max_b_frames = C.int(val) + return cc +} + +func (cc *CodecCtx) SetPixFmt(val int32) *CodecCtx { + cc.avCodecCtx.pix_fmt = val + return cc +} + +func (cc *CodecCtx) SetFlag(flag int) *CodecCtx { + cc.avCodecCtx.flags |= C.int(flag) + return cc +} + +func (cc *CodecCtx) SetMbDecision(val int) *CodecCtx { + cc.avCodecCtx.mb_decision = C.int(val) + return cc +} + +func (cc *CodecCtx) SetSampleFmt(val int32) *CodecCtx { + if int(C.check_sample_fmt(cc.codec.avCodec, val)) == 0 { + panic(fmt.Sprintf("encoder doesn't support sample format %s", GetSampleFmtName(val))) + } + + cc.avCodecCtx.sample_fmt = val + return cc +} + +func (cc *CodecCtx) SetSampleRate(val int) *CodecCtx { + cc.avCodecCtx.sample_rate = C.int(val) + return cc +} + +var ( + FF_COMPLIANCE_VERY_STRICT int = C.FF_COMPLIANCE_VERY_STRICT + FF_COMPLIANCE_STRICT int = C.FF_COMPLIANCE_STRICT + FF_COMPLIANCE_NORMAL int = C.FF_COMPLIANCE_NORMAL + FF_COMPLIANCE_UNOFFICIAL int = C.FF_COMPLIANCE_UNOFFICIAL + FF_COMPLIANCE_EXPERIMENTAL int = C.FF_COMPLIANCE_EXPERIMENTAL +) + +func (cc *CodecCtx) SetStrictCompliance(val int) *CodecCtx { + cc.avCodecCtx.strict_std_compliance = C.int(val) + return cc +} + +func (cc *CodecCtx) SetHasBframes(val int) *CodecCtx { + cc.avCodecCtx.has_b_frames = C.int(val) + return cc +} + +func (cc *CodecCtx) SetChannels(val int) *CodecCtx { + cc.avCodecCtx.channels = C.int(val) + return cc +} + +func (cc *CodecCtx) SetFrameRate(r AVR) *CodecCtx { + cc.avCodecCtx.framerate.num = C.int(r.Num) + cc.avCodecCtx.framerate.den = C.int(r.Den) + return cc +} + +func (cc *CodecCtx) SetBitsPerRawSample(val int) *CodecCtx { + cc.avCodecCtx.bits_per_raw_sample = C.int(val) + return cc +} + +func (cc *CodecCtx) SelectSampleRate() int { + return int(C.select_sample_rate(cc.codec.avCodec)) +} + +func (cc *CodecCtx) SelectChannelLayout() int { + return int(C.select_channel_layout(cc.codec.avCodec)) +} + +func (cc *CodecCtx) FlushBuffers() { + C.avcodec_flush_buffers(cc.avCodecCtx) +} + +func (cc *CodecCtx) Dump() { + fmt.Println(cc.avCodecCtx) +} + +func (cc *CodecCtx) GetFrameRate() AVRational { + return AVRational(cc.avCodecCtx.framerate) +} + +func (cc *CodecCtx) GetProfile() int { + return int(cc.avCodecCtx.profile) +} + +func (cc *CodecCtx) GetProfileName() string { + return C.GoString(C.avcodec_profile_name(cc.avCodecCtx.codec_id, cc.avCodecCtx.profile)) +} + +func (cc *CodecCtx) GetMediaType() string { + return C.GoString(C.av_get_media_type_string(cc.avCodecCtx.codec_type)) +} + +func (cc *CodecCtx) GetCodecTag() uint32 { + return uint32(cc.avCodecCtx.codec_tag) +} + +func (cc *CodecCtx) GetCodecTagName() string { + var ( + ct uint32 = uint32(cc.avCodecCtx.codec_tag) + result string + ) + + for i := 0; i < 4; i++ { + c := ct & 0xff + result += fmt.Sprintf("%c", c) + ct >>= 8 + } + + return fmt.Sprintf("%v", result) +} + +func (cc *CodecCtx) GetCodedWith() int { + return int(cc.avCodecCtx.coded_width) +} + +func (cc *CodecCtx) GetCodedHeight() int { + return int(cc.avCodecCtx.coded_height) +} + +func (cc *CodecCtx) GetBFrames() int { + return int(cc.avCodecCtx.has_b_frames) +} + +func (cc *CodecCtx) GetPixFmtName() string { + // return C.GoString(C.av_get_pix_fmt_name(cc.avCodecCtx.pix_fmt)) + return "unknown" +} + +func (cc *CodecCtx) GetColorRangeName() string { + return color_range_names[cc.avCodecCtx.color_range] +} + +func (cc *CodecCtx) GetRefs() int { + return int(cc.avCodecCtx.refs) +} + +func (cc *CodecCtx) GetSampleFmtName() string { + return C.GoString(C.av_get_sample_fmt_name(cc.avCodecCtx.sample_fmt)) +} + +func (cc *CodecCtx) GetChannelLayoutName() string { + str := C.GoString(C.gmf_get_channel_layout_name(C.int(cc.Channels()), C.int(cc.ChannelLayout()))) + + return str +} + +func (this *CodecCtx) GetDefaultChannelLayout(ac int) int { + return int(C.av_get_default_channel_layout(C.int(ac))) +} + +func (cc *CodecCtx) GetBitsPerSample() int { + return int(C.av_get_bits_per_sample(cc.codec.avCodec.id)) +} + +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 + result []*Frame = make([]*Frame, 0) + ) + + 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, AvError(ret) + } + + for { + frame := NewFrame() + + ret = int(C.avcodec_receive_frame(cc.avCodecCtx, frame.avFrame)) + 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) + } + + return result, nil +} + +func (cc *CodecCtx) Encode(frames []*Frame, drain int) ([]*Packet, error) { + var ( + ret int + result []*Packet = make([]*Packet, 0) + ) + + if len(frames) == 0 && drain >= 0 { + frames = append(frames, nil) + } + + for _, frame := range frames { + if frame == nil { + ret = int(C.avcodec_send_frame(cc.avCodecCtx, nil)) + } else { + ret = int(C.avcodec_send_frame(cc.avCodecCtx, frame.avFrame)) + } + if ret < 0 { + return nil, AvError(ret) + } + + for { + pkt := NewPacket() + ret = int(C.avcodec_receive_packet(cc.avCodecCtx, &pkt.avPacket)) + if ret < 0 { + pkt.Free() + break + } + + result = append(result, pkt) + } + if frame != nil { + frame.Free() + } + } + + 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 +} diff --git a/filter.go b/filter_go111.go similarity index 99% rename from filter.go rename to filter_go111.go index 6fddf2a..314b564 100644 --- a/filter.go +++ b/filter_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + package gmf /* diff --git a/filter_go112.go b/filter_go112.go new file mode 100644 index 0000000..67f8168 --- /dev/null +++ b/filter_go112.go @@ -0,0 +1,213 @@ +// +build go1.12 + +package gmf + +/* + +#cgo pkg-config: libavfilter + +#include +#include +#include +#include + +*/ +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 []*C.AVFilterContext + sinkCtx *C.AVFilterContext + filterGraph *C.AVFilterGraph +} + +func NewFilter(desc string, srcStreams []*Stream, ost *Stream, options []*Option) (*Filter, error) { + f := &Filter{ + filterGraph: C.avfilter_graph_alloc(), + bufferCtx: make([]*C.AVFilterContext, 0), + } + + var ( + ret, i int + args string + inputs *C.AVFilterInOut + outputs *C.AVFilterInOut + curr *C.AVFilterInOut + last *C.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) (*C.AVFilterContext, int) { + var ( + ctx *C.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) +} diff --git a/format.go b/format_go111.go similarity index 99% rename from format.go rename to format_go111.go index 8090639..cec79ad 100644 --- a/format.go +++ b/format_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + // Format. package gmf diff --git a/format_go112.go b/format_go112.go new file mode 100644 index 0000000..44ba190 --- /dev/null +++ b/format_go112.go @@ -0,0 +1,603 @@ +// +build go1.12 + +// Format. +package gmf + +/* + +#cgo pkg-config: libavformat libavdevice libavfilter + +#include +#include "libavformat/avformat.h" +#include +#include "libavutil/opt.h" + +static AVStream* gmf_get_stream(AVFormatContext *ctx, int idx) { + return ctx->streams[idx]; +} + +static int gmf_alloc_priv_data(AVFormatContext *s, AVDictionary **options) { + AVDictionary *tmp = NULL; + + if (options) + av_dict_copy(&tmp, *options, 0); + + if (s->iformat->priv_data_size > 0) { + if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) { + return -1; + } + + if (s->iformat->priv_class) { + *(const AVClass**)s->priv_data = s->iformat->priv_class; + av_opt_set_defaults(s->priv_data); + if (av_opt_set_dict(s->priv_data, &tmp) < 0) + return -1; + } + + return (s->iformat->priv_data_size); + } + + return 0; +} + +static char *gmf_sprintf_sdp(AVFormatContext *ctx) { + char *sdp = malloc(sizeof(char)*16384); + av_sdp_create(&ctx, 1, sdp, sizeof(char)*16384); + return sdp; +} +*/ +import "C" + +import ( + "errors" + "fmt" + "io" + "os" + "syscall" + "time" + "unsafe" +) + +const ( + AV_LOG_QUIET int = C.AV_LOG_QUIET + AV_LOG_PANIC int = C.AV_LOG_PANIC + AV_LOG_FATAL int = C.AV_LOG_FATAL + AV_LOG_ERROR int = C.AV_LOG_ERROR + AV_LOG_WARNING int = C.AV_LOG_WARNING + AV_LOG_INFO int = C.AV_LOG_INFO + AV_LOG_VERBOSE int = C.AV_LOG_VERBOSE + AV_LOG_DEBUG int = C.AV_LOG_DEBUG + + FF_MOV_FLAG_FASTSTART = (1 << 7) + + AVFMT_FLAG_GENPTS int = C.AVFMT_FLAG_GENPTS + AVFMTCTX_NOHEADER int = C.AVFMTCTX_NOHEADER +) + +const ( + PLAYLIST_TYPE_NONE int = iota + PLAYLIST_TYPE_EVENT + PLAYLIST_TYPE_VOD + PLAYLIST_TYPE_NB +) + +type FmtCtx struct { + Filename string + + avCtx *C.struct_AVFormatContext + ofmt *OutputFmt + streams map[int]*Stream + customPb bool +} + +func init() { + C.avformat_network_init() + C.avdevice_register_all() +} + +func LogSetLevel(level int) { + C.av_log_set_level(C.int(level)) +} + +// @todo return error if avCtx is null +// @todo start_time is it needed? +func NewCtx(options ...[]Option) *FmtCtx { + ctx := &FmtCtx{ + avCtx: C.avformat_alloc_context(), + streams: make(map[int]*Stream), + customPb: false, + } + + ctx.avCtx.start_time = 0 + + if len(options) == 1 { + for _, option := range options[0] { + option.Set(ctx.avCtx) + } + } + + return ctx +} + +func NewOutputCtx(i interface{}, options ...[]Option) (*FmtCtx, error) { + this := &FmtCtx{streams: make(map[int]*Stream)} + + switch t := i.(type) { + case string: + this.ofmt = FindOutputFmt("", i.(string), "") + + case *OutputFmt: + this.ofmt = i.(*OutputFmt) + + default: + return nil, errors.New(fmt.Sprintf("unexpected type %v", t)) + } + + if this.ofmt == nil { + return nil, errors.New(fmt.Sprintf("output format is not initialized. Unable to allocate context")) + } + + cfilename := C.CString(this.ofmt.Filename) + defer C.free(unsafe.Pointer(cfilename)) + + C.avformat_alloc_output_context2(&this.avCtx, this.ofmt.avOutputFmt, nil, cfilename) + if this.avCtx == nil { + return nil, errors.New(fmt.Sprintf("unable to allocate context")) + } + + C.av_opt_set_defaults(unsafe.Pointer(this.avCtx)) + + if len(options) == 1 { + for _, option := range options[0] { + option.Set(this.avCtx) + } + } + + this.Filename = this.ofmt.Filename + + return this, nil +} + +func NewOutputCtxWithFormatName(filename, format string) (*FmtCtx, error) { + this := &FmtCtx{streams: make(map[int]*Stream)} + + cfilename := C.CString(filename) + defer C.free(unsafe.Pointer(cfilename)) + + cFormat := C.CString(format) + defer C.free(unsafe.Pointer(cFormat)) + + C.avformat_alloc_output_context2(&this.avCtx, nil, cFormat, cfilename) + + if this.avCtx == nil { + return nil, errors.New(fmt.Sprintf("unable to allocate context")) + } + + this.Filename = filename + + this.ofmt = &OutputFmt{Filename: filename, avOutputFmt: this.avCtx.oformat} + + return this, nil +} + +// Just a helper for NewCtx().OpenInput() +func NewInputCtx(filename string) (*FmtCtx, error) { + ctx := NewCtx() + + if ctx.avCtx == nil { + return nil, errors.New(fmt.Sprintf("unable to allocate context")) + } + + if err := ctx.OpenInput(filename); err != nil { + return nil, err + } + + return ctx, nil +} + +func NewInputCtxWithFormatName(filename, format string) (*FmtCtx, error) { + ctx := NewCtx() + + if ctx.avCtx == nil { + return nil, errors.New(fmt.Sprintf("unable to allocate context")) + } + if err := ctx.SetInputFormat(format); err != nil { + return nil, err + } + if err := ctx.OpenInput(filename); err != nil { + return nil, err + } + return ctx, nil +} + +func (this *FmtCtx) SetOptions(options []*Option) { + for _, option := range options { + option.Set(this.avCtx) + } +} + +func (this *FmtCtx) OpenInput(filename string) error { + var ( + cfilename *C.char + options *C.struct_AVDictionary = nil + ) + + if filename == "" { + cfilename = nil + } else { + cfilename = C.CString(filename) + defer C.free(unsafe.Pointer(cfilename)) + } + + if averr := C.avformat_open_input(&this.avCtx, cfilename, nil, &options); averr < 0 { + return errors.New(fmt.Sprintf("Error opening input '%s': %s", filename, AvError(int(averr)))) + } + + if averr := C.avformat_find_stream_info(this.avCtx, nil); averr < 0 { + return errors.New(fmt.Sprintf("Unable to find stream info: %s", AvError(int(averr)))) + } + + return nil +} + +func (this *FmtCtx) AddStreamWithCodeCtx(codeCtx *CodecCtx) (*Stream, error) { + var ost *Stream + + // Create Video stream in output context + if ost = this.NewStream(codeCtx.Codec()); ost == nil { + return nil, fmt.Errorf("unable to create stream in context, filename: %s", this.Filename) + } + + ost.DumpContexCodec(codeCtx) + + if this.avCtx.oformat != nil && int(this.avCtx.oformat.flags&C.AVFMT_GLOBALHEADER) > 0 { + ost.SetCodecFlags() + } + + return ost, nil +} + +func (this *FmtCtx) WriteTrailer() { + C.av_write_trailer(this.avCtx) +} + +func (this *FmtCtx) IsNoFile() bool { + return this.avCtx.oformat != nil && (this.avCtx.oformat.flags&C.AVFMT_NOFILE) != 0 +} + +func (this *FmtCtx) IsGlobalHeader() bool { + return this.avCtx != nil && this.avCtx.oformat != nil && (this.avCtx.oformat.flags&C.AVFMT_GLOBALHEADER) != 0 +} + +func (this *FmtCtx) WriteHeader() error { + cfilename := &(this.avCtx.filename[0]) + + // If NOFILE flag isn't set and we don't use custom IO, open it + if !this.IsNoFile() && !this.customPb { + if averr := C.avio_open(&this.avCtx.pb, cfilename, C.AVIO_FLAG_WRITE); averr < 0 { + return errors.New(fmt.Sprintf("Unable to open '%s': %s", this.Filename, AvError(int(averr)))) + } + } + + if averr := C.avformat_write_header(this.avCtx, nil); averr < 0 { + return errors.New(fmt.Sprintf("Unable to write header to '%s': %s", this.Filename, AvError(int(averr)))) + } + + return nil +} + +func (this *FmtCtx) WritePacket(p *Packet) error { + if averr := C.av_interleaved_write_frame(this.avCtx, &p.avPacket); averr < 0 { + return errors.New(fmt.Sprintf("Unable to write packet to '%s': %s", this.Filename, AvError(int(averr)))) + } + + return nil +} + +func (this *FmtCtx) WritePacketNoBuffer(p *Packet) error { + if averr := C.av_write_frame(this.avCtx, &p.avPacket); averr < 0 { + return errors.New(fmt.Sprintf("Unable to write packet to '%s': %s", this.Filename, AvError(int(averr)))) + } + + return nil +} + +func (this *FmtCtx) SetOformat(ofmt *OutputFmt) error { + if ofmt == nil { + return errors.New("'ofmt' is not initialized.") + } + + if averr := C.avformat_alloc_output_context2(&this.avCtx, ofmt.avOutputFmt, nil, nil); averr < 0 { + return errors.New(fmt.Sprintf("Error creating output context: %s", AvError(int(averr)))) + } + + return nil +} + +func (this *FmtCtx) Dump() { + if this.ofmt == nil { + C.av_dump_format(this.avCtx, 0, &(this.avCtx.filename[0]), 0) + } else { + C.av_dump_format(this.avCtx, 0, &(this.avCtx.filename[0]), 1) + } +} + +func (this *FmtCtx) DumpAv() { + fmt.Println("AVCTX:\n", this.avCtx, "\niformat:\n", this.avCtx.iformat) + fmt.Println("flags:", this.avCtx.flags) +} + +func (this *FmtCtx) GetNextPacket() (*Packet, error) { + pkt := NewPacket() + + for { + ret := int(C.av_read_frame(this.avCtx, &pkt.avPacket)) + + if AvErrno(ret) == syscall.EAGAIN { + time.Sleep(10000 * time.Microsecond) + continue + } + if ret == AVERROR_EOF { + return nil, io.EOF + } + if ret < 0 { + return nil, AvError(ret) + } + + break + } + + return pkt, nil +} + +func (this *FmtCtx) NewStream(c *Codec) *Stream { + var avCodec *C.struct_AVCodec = nil + + if c != nil { + avCodec = c.avCodec + } + + if st := C.avformat_new_stream(this.avCtx, avCodec); st == nil { + return nil + } else { + this.streams[int(st.index)] = &Stream{avStream: st} + Retain(this.streams[int(st.index)]) + return this.streams[int(st.index)] + } + +} + +// Original structure member is called instead of len(this.streams) +// because there is no initialized Stream wrappers in input context. +func (this *FmtCtx) StreamsCnt() int { + return int(this.avCtx.nb_streams) +} + +func (this *FmtCtx) GetStream(idx int) (*Stream, error) { + if idx > this.StreamsCnt()-1 || this.StreamsCnt() == 0 { + return nil, errors.New(fmt.Sprintf("Stream index '%d' is out of range. There is only '%d' streams.", idx, this.StreamsCnt())) + } + + if _, ok := this.streams[idx]; !ok { + // create instance of Stream wrapper, when stream was initialized + // by demuxer. it means that this is an input context. + this.streams[idx] = &Stream{ + avStream: C.gmf_get_stream(this.avCtx, C.int(idx)), + } + } + + return this.streams[idx], nil +} + +func (this *FmtCtx) GetBestStream(typ int32) (*Stream, error) { + idx := C.av_find_best_stream(this.avCtx, typ, -1, -1, nil, 0) + if int(idx) < 0 { + return nil, errors.New(fmt.Sprintf("stream type %d not found", typ)) + } + + return this.GetStream(int(idx)) +} + +func (this *FmtCtx) FindStreamInfo() error { + if averr := C.avformat_find_stream_info(this.avCtx, nil); averr < 0 { + return errors.New(fmt.Sprintf("unable to find stream info: %s", AvError(int(averr)))) + } + + return nil +} + +func (this *FmtCtx) SetInputFormat(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + if this.avCtx.iformat = (*C.struct_AVInputFormat)(C.av_find_input_format(cname)); this.avCtx.iformat == nil { + return errors.New("unable to find format for name: " + name) + } + + if int(C.gmf_alloc_priv_data(this.avCtx, nil)) < 0 { + return errors.New("unable to allocate priv_data") + } + + return nil +} + +func (this *FmtCtx) Close() { + if this.ofmt == nil { + this.CloseInput() + } else { + this.CloseOutput() + } +} + +func (this *FmtCtx) CloseInput() { + if this.avCtx != nil { + C.avformat_close_input(&this.avCtx) + } +} + +func (this *FmtCtx) CloseOutput() { + if this.avCtx == nil { + return + } + if this.IsNoFile() { + return + } + + if this.avCtx.pb != nil && !this.customPb { + C.avio_close(this.avCtx.pb) + } +} + +func (this *FmtCtx) Free() { + this.Close() + + if this.avCtx != nil { + C.avformat_free_context(this.avCtx) + } +} + +func (this *FmtCtx) Duration() int { + us := int(this.avCtx.duration) % AV_TIME_BASE + fmt.Printf("us: %v\n", us) + fmt.Printf("duration: %v\n", int(this.avCtx.duration)) + return int(this.avCtx.duration) / AV_TIME_BASE +} + +// Total stream bitrate in bit/s +func (this *FmtCtx) BitRate() int64 { + return int64(this.avCtx.bit_rate) +} + +func (this *FmtCtx) StartTime() int { + return int(this.avCtx.start_time) +} + +func (this *FmtCtx) SetStartTime(val int) *FmtCtx { + this.avCtx.start_time = C.int64_t(val) + return this +} + +func (this *FmtCtx) TsOffset(stime int) int { + // temp solution. see ffmpeg_opt.c:899 + return (0 - stime) +} + +func (this *FmtCtx) SetDebug(val int) *FmtCtx { + this.avCtx.debug = C.int(val) + return this +} + +func (this *FmtCtx) SetFlag(flag int) *FmtCtx { + this.avCtx.flags |= C.int(flag) + return this +} + +func (this *FmtCtx) SeekFile(ist *Stream, minTs, maxTs int64, flag int) error { + if ret := int(C.avformat_seek_file(this.avCtx, C.int(ist.Index()), C.int64_t(0), C.int64_t(minTs), C.int64_t(maxTs), C.int(flag))); ret < 0 { + return errors.New(fmt.Sprintf("Error creating output context: %s", AvError(ret))) + } + + return nil +} + +func (this *FmtCtx) SeekFrameAt(sec int64, streamIndex int) error { + ist, err := this.GetStream(streamIndex) + if err != nil { + return err + } + + frameTs := Rescale(sec*1000, int64(ist.TimeBase().AVR().Den), int64(ist.TimeBase().AVR().Num)) / 1000 + + if err := this.SeekFile(ist, frameTs, frameTs, C.AVSEEK_FLAG_FRAME); err != nil { + return err + } + + ist.CodecCtx().FlushBuffers() + + return nil +} + +func (this *FmtCtx) SetPb(val *AVIOContext) *FmtCtx { + this.avCtx.pb = val.avAVIOContext + this.customPb = true + return this +} + +func (this *FmtCtx) GetSDPString() (sdp string) { + sdpChar := C.gmf_sprintf_sdp(this.avCtx) + defer C.free(unsafe.Pointer(sdpChar)) + + return C.GoString(sdpChar) +} + +func (this *FmtCtx) WriteSDPFile(filename string) error { + file, err := os.Create(filename) + if err != nil { + return errors.New(fmt.Sprintf("Error open file:%s,error message:%s", filename, err)) + } + defer file.Close() + + file.WriteString(this.GetSDPString()) + return nil +} + +func (this *FmtCtx) Position() int { + return int(this.avCtx.pb.pos) +} + +func (this *FmtCtx) SetProbeSize(v int64) { + this.avCtx.probesize = C.int64_t(v) +} + +func (this *FmtCtx) GetProbeSize() int64 { + return int64(this.avCtx.probesize) +} + +type OutputFmt struct { + Filename string + avOutputFmt *C.struct_AVOutputFormat + CgoMemoryManage +} + +func FindOutputFmt(format string, filename string, mime string) *OutputFmt { + cformat := C.CString(format) + defer C.free(unsafe.Pointer(cformat)) + + cfilename := C.CString(filename) + defer C.free(unsafe.Pointer(cfilename)) + + cmime := C.CString(mime) + defer C.free(unsafe.Pointer(cmime)) + + var ofmt *C.struct_AVOutputFormat + + if ofmt = C.av_guess_format(cformat, nil, cmime); ofmt == nil { + ofmt = C.av_guess_format(nil, cfilename, cmime) + } + + if ofmt == nil { + return nil + } + + return &OutputFmt{Filename: filename, avOutputFmt: ofmt} +} + +func (this *OutputFmt) Free() { + fmt.Printf("(this *OutputFmt)Free()\n") +} + +func (this *OutputFmt) Name() string { + return C.GoString(this.avOutputFmt.name) +} + +func (this *OutputFmt) LongName() string { + return C.GoString(this.avOutputFmt.long_name) +} + +func (this *OutputFmt) MimeType() string { + return C.GoString(this.avOutputFmt.mime_type) +} + +func (this *OutputFmt) Infomation() string { + return this.Filename + ":" + this.Name() + "#" + this.LongName() + "#" + this.MimeType() +} diff --git a/frame.go b/frame_go111.go similarity index 99% rename from frame.go rename to frame_go111.go index e6dd73b..f86491f 100644 --- a/frame.go +++ b/frame_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + package gmf /* diff --git a/frame_go112.go b/frame_go112.go new file mode 100644 index 0000000..e447fb5 --- /dev/null +++ b/frame_go112.go @@ -0,0 +1,259 @@ +// +build go1.12 + +package gmf + +/* +#cgo pkg-config: libavcodec libavutil + +#include "libavcodec/avcodec.h" +#include "libavutil/frame.h" +#include "libavutil/imgutils.h" +#include "libavutil/timestamp.h" + +void gmf_set_frame_data(AVFrame *frame, int idx, int l_size, uint8_t data) { + if(!frame) { + fprintf(stderr, "frame is NULL\n"); + } + + frame->data[idx][l_size] = data; +} + +int gmf_get_frame_line_size(AVFrame *frame, int idx) { + return frame->linesize[idx]; +} + +void gmf_free_data(AVFrame *frame) { + av_freep(&frame->data[0]); +} + +*/ +import "C" + +import ( + "errors" + "fmt" + "syscall" + "unsafe" +) + +type Frame struct { + avFrame *C.struct_AVFrame + samples *C.uint8_t + mediaType int32 + err error + freeData bool +} + +func NewFrame() *Frame { + return &Frame{avFrame: C.av_frame_alloc()} +} + +func (f *Frame) Encode(enc *CodecCtx) (*Packet, error) { + pkt := NewPacket() + if pkt == nil { + return nil, fmt.Errorf("unable to initialize new packet") + } + + if ret := int(C.avcodec_send_frame(enc.avCodecCtx, f.avFrame)); ret < 0 { + return nil, fmt.Errorf("error sending frame - %v", AvErrno(ret)) + } + + for { + ret := int(C.avcodec_receive_packet(enc.avCodecCtx, &pkt.avPacket)) + if AvErrno(ret) == syscall.EAGAIN { + return nil, nil + } + if ret < 0 { + return nil, fmt.Errorf("%v", AvErrno(ret)) + } + if ret >= 0 { + break + } + } + + return pkt, nil +} + +func (f *Frame) Pts() int64 { + return int64(f.avFrame.pts) +} + +func (f *Frame) Unref() { + C.av_frame_unref(f.avFrame) +} + +func (f *Frame) SetPts(val int64) { + f.avFrame.pts = (C.int64_t)(val) +} + +// AVPixelFormat for video frames, AVSampleFormat for audio +func (f *Frame) Format() int { + return int(f.avFrame.format) +} + +func (f *Frame) Width() int { + return int(f.avFrame.width) +} + +func (f *Frame) Height() int { + return int(f.avFrame.height) +} + +func (f *Frame) PktPts() int64 { + return int64(f.avFrame.pkt_pts) +} + +func (f *Frame) SetPktPts(val int64) { + f.avFrame.pkt_pts = (C.int64_t)(val) +} + +func (f *Frame) PktDts() int { + return int(f.avFrame.pkt_dts) +} + +func (f *Frame) SetPktDts(val int) { + f.avFrame.pkt_dts = (C.int64_t)(val) +} + +func (f *Frame) KeyFrame() int { + return int(f.avFrame.key_frame) +} + +func (f *Frame) NbSamples() int { + return int(f.avFrame.nb_samples) +} + +func (f *Frame) Channels() int { + return int(f.avFrame.channels) +} + +func (f *Frame) SetFormat(val int32) *Frame { + f.avFrame.format = C.int(val) + return f +} + +func (f *Frame) SetWidth(val int) *Frame { + f.avFrame.width = C.int(val) + return f +} + +func (f *Frame) SetHeight(val int) *Frame { + f.avFrame.height = C.int(val) + return f +} + +func (f *Frame) ImgAlloc() error { + if ret := int(C.av_image_alloc( + (**C.uint8_t)(unsafe.Pointer(&f.avFrame.data)), + (*C.int)(unsafe.Pointer(&f.avFrame.linesize)), + C.int(f.Width()), C.int(f.Height()), int32(f.Format()), 32)); ret < 0 { + return errors.New(fmt.Sprintf("Unable to allocate raw image buffer: %v", AvError(ret))) + } + + f.freeData = true + + return nil +} + +func NewAudioFrame(sampleFormat int32, channels, nb_samples int) (*Frame, error) { + f := NewFrame() + f.mediaType = AVMEDIA_TYPE_AUDIO + f.SetNbSamples(nb_samples) + f.SetFormat(sampleFormat) + f.SetChannels(channels) + + //the codec gives us the frame size, in samples, + //we calculate the size of the samples buffer in bytes + size := C.av_samples_get_buffer_size(nil, C.int(channels), C.int(nb_samples), + sampleFormat, 0) + if size < 0 { + return nil, errors.New("could not get sample buffer size") + } + + f.samples = (*C.uint8_t)(C.av_malloc(C.size_t(size))) + if f.samples == nil { + return nil, errors.New(fmt.Sprintf("could not allocate %d bytes for samples buffer", size)) + } + + //setup the data pointers in the AVFrame + ret := int(C.avcodec_fill_audio_frame(f.avFrame, C.int(channels), sampleFormat, + f.samples, C.int(size), 0)) + if ret < 0 { + return nil, errors.New("could not setup audio frame") + } + + f.freeData = true + + return f, nil +} + +func (f *Frame) SetData(idx int, lineSize int, data int) *Frame { + C.gmf_set_frame_data(f.avFrame, C.int(idx), C.int(lineSize), (C.uint8_t)(data)) + + return f +} + +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)} +} + +func (f *Frame) Free() { + if f.freeData && f.avFrame != nil { + C.gmf_free_data(f.avFrame) + } + if f.avFrame != nil { + C.av_frame_free(&f.avFrame) + } +} + +func (f *Frame) SetNbSamples(val int) *Frame { + f.avFrame.nb_samples = C.int(val) + return f +} + +func (f *Frame) SetChannelLayout(val int) *Frame { + f.avFrame.channel_layout = (C.uint64_t)(val) + return f +} + +func (f *Frame) GetChannelLayout() int { + return int(f.avFrame.channel_layout) +} + +func (f *Frame) SetChannels(val int) *Frame { + f.avFrame.channels = C.int(val) + return f +} + +func (f *Frame) SetQuality(val int) *Frame { + f.avFrame.quality = C.int(val) + return f +} + +func (f *Frame) SetPictType() { + f.avFrame.pict_type = C.AV_PICTURE_TYPE_NONE +} + +func (f *Frame) IsNil() bool { + if f.avFrame == nil { + return true + } + + return false +} + +func (f *Frame) GetRawFrame() *C.struct_AVFrame { + return f.avFrame +} + +func (f *Frame) Time(timebase AVRational) int { + return int(float64(timebase.AVR().Num) / float64(timebase.AVR().Den) * float64(f.Pts())) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fc8b91e --- /dev/null +++ b/go.mod @@ -0,0 +1,2 @@ +module github.com/3d0c/gmf + diff --git a/packet.go b/packet_go111.go similarity index 98% rename from packet.go rename to packet_go111.go index d727446..0191a34 100644 --- a/packet.go +++ b/packet_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + package gmf /* diff --git a/packet_go112.go b/packet_go112.go new file mode 100644 index 0000000..7ea384a --- /dev/null +++ b/packet_go112.go @@ -0,0 +1,109 @@ +// +build go1.12 + +package gmf + +/* +#cgo pkg-config: libavcodec + +#include "libavcodec/avcodec.h" + +void shift_data(AVPacket *pkt, int offset) { + pkt->data += offset; + pkt->size -= offset; + + return; +} + +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +type Packet struct { + avPacket C.struct_AVPacket +} + +func NewPacket() *Packet { + p := &Packet{} + + C.av_init_packet(&p.avPacket) + + p.avPacket.data = nil + p.avPacket.size = 0 + + return p +} + +func (p *Packet) Pts() int64 { + return int64(p.avPacket.pts) +} + +func (p *Packet) SetPts(pts int64) { + p.avPacket.pts = C.int64_t(pts) +} + +func (p *Packet) Dts() int64 { + return int64(p.avPacket.dts) +} + +func (p *Packet) SetDts(val int64) { + p.avPacket.dts = C.int64_t(val) +} + +func (p *Packet) Flags() int { + return int(p.avPacket.flags) +} + +func (p *Packet) Duration() int64 { + return int64(p.avPacket.duration) +} + +func (p *Packet) SetDuration(duration int64) { + p.avPacket.duration = C.int64_t(duration) +} + +func (p *Packet) StreamIndex() int { + return int(p.avPacket.stream_index) +} + +func (p *Packet) Size() int { + return int(p.avPacket.size) +} + +func (p *Packet) Pos() int64 { + return int64(p.avPacket.pos) +} + +func (p *Packet) Data() []byte { + return C.GoBytes(unsafe.Pointer(p.avPacket.data), C.int(p.avPacket.size)) +} + +func (p *Packet) Clone() *Packet { + np := NewPacket() + + C.av_packet_ref(&np.avPacket, &p.avPacket) + + return np +} + +func (p *Packet) Dump() { + fmt.Printf("idx: %d\npts: %d\ndts: %d\nsize: %d\nduration:%d\npos:%d\ndata: % x\n", p.StreamIndex(), p.avPacket.pts, p.avPacket.dts, p.avPacket.size, p.avPacket.duration, p.avPacket.pos, C.GoBytes(unsafe.Pointer(p.avPacket.data), 128)) + fmt.Println("------------------------------") + +} + +func (p *Packet) SetStreamIndex(val int) *Packet { + p.avPacket.stream_index = C.int(val) + return p +} + +func (p *Packet) Free() { + C.av_packet_unref(&p.avPacket) +} + +func (p *Packet) Time(timebase AVRational) int { + return int(float64(timebase.AVR().Num) / float64(timebase.AVR().Den) * float64(p.Pts())) +} diff --git a/samplefmt.go b/samplefmt_go111.go similarity index 97% rename from samplefmt.go rename to samplefmt_go111.go index 0d055a7..ec0418f 100644 --- a/samplefmt.go +++ b/samplefmt_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + package gmf /* diff --git a/samplefmt_go112.go b/samplefmt_go112.go new file mode 100644 index 0000000..54a2bcf --- /dev/null +++ b/samplefmt_go112.go @@ -0,0 +1,56 @@ +// +build go1.12 + +package gmf + +/* + +#cgo pkg-config: libavutil + +#include "libavutil/samplefmt.h" + +*/ +import "C" + +import ( + "errors" + "fmt" + "unsafe" +) + +// +// Unfinished. +// + +type SampleFormat int + +type Sample struct { + data **C.uint8_t + linesize *int + format SampleFormat + CgoMemoryManage +} + +func NewSample(nbSamples, nbChannels int, format SampleFormat) error { + panic("This stuff is unfinished.") + this := &Sample{format: format} + + if ret := int(C.av_samples_alloc_array_and_samples( + &this.data, + (*C.int)(unsafe.Pointer(&this.linesize)), + C.int(nbChannels), C.int(nbSamples), int32(format), 0)); ret < 0 { + return errors.New(fmt.Sprintf("Unable to allocate array and samples: %v", AvError(ret))) + } + + return nil +} + +func (this *Sample) SampleRealloc(nbSamples, nbChannels int) error { + if ret := int(C.av_samples_alloc( + this.data, + (*C.int)(unsafe.Pointer(&this.linesize)), + C.int(nbChannels), C.int(nbSamples), int32(this.format), 0)); ret < 0 { + return errors.New(fmt.Sprintf("Unable to allocate samples: %v", AvError(ret))) + } + + return nil +} diff --git a/sws.go b/sws_go111.go similarity index 98% rename from sws.go rename to sws_go111.go index 0faab4f..34899c4 100644 --- a/sws.go +++ b/sws_go111.go @@ -1,3 +1,5 @@ +// +build go1.11,!go1.12 + package gmf /* diff --git a/sws_go112.go b/sws_go112.go new file mode 100644 index 0000000..d016047 --- /dev/null +++ b/sws_go112.go @@ -0,0 +1,108 @@ +// +build go1.12 + +package gmf + +/* + +#cgo pkg-config: libswscale + +#include "libswscale/swscale.h" + +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +var ( + SWS_FAST_BILINEAR int = C.SWS_FAST_BILINEAR + SWS_BILINEAR int = C.SWS_BILINEAR + SWS_BICUBIC int = C.SWS_BICUBIC + SWS_X int = C.SWS_X + SWS_POINT int = C.SWS_POINT + SWS_AREA int = C.SWS_AREA + SWS_BICUBLIN int = C.SWS_BICUBLIN + SWS_GAUSS int = C.SWS_GAUSS + SWS_SINC int = C.SWS_SINC + SWS_LANCZOS int = C.SWS_LANCZOS + SWS_SPLINE int = C.SWS_SPLINE +) + +type SwsCtx struct { + swsCtx *C.struct_SwsContext + width int + height int + pixfmt int32 +} + +func NewSwsCtx(srcW, srcH int, srcPixFmt int32, dstW, dstH int, dstPixFmt int32, method int) (*SwsCtx, error) { + ctx := C.sws_getContext( + C.int(srcW), + C.int(srcH), + srcPixFmt, + C.int(dstW), + C.int(dstH), + dstPixFmt, + C.int(method), nil, nil, nil, + ) + + if ctx == nil { + return nil, fmt.Errorf("error creating sws context\n") + } + + return &SwsCtx{ + swsCtx: ctx, + width: dstW, + height: dstH, + pixfmt: dstPixFmt, + }, nil +} + +func (ctx *SwsCtx) Scale(src *Frame, dst *Frame) { + C.sws_scale( + ctx.swsCtx, + (**C.uint8_t)(unsafe.Pointer(&src.avFrame.data)), + (*C.int)(unsafe.Pointer(&src.avFrame.linesize)), + 0, + C.int(src.Height()), + (**C.uint8_t)(unsafe.Pointer(&dst.avFrame.data)), + (*C.int)(unsafe.Pointer(&dst.avFrame.linesize))) +} + +func (ctx *SwsCtx) Free() { + if ctx.swsCtx != nil { + C.sws_freeContext(ctx.swsCtx) + } +} + +func DefaultRescaler(ctx *SwsCtx, frames []*Frame) ([]*Frame, error) { + var ( + result []*Frame = make([]*Frame, 0) + tmp *Frame + err error + ) + + for i, _ := range frames { + tmp = NewFrame().SetWidth(ctx.width).SetHeight(ctx.height).SetFormat(ctx.pixfmt) + if err = tmp.ImgAlloc(); err != nil { + return nil, fmt.Errorf("error allocation tmp frame - %s", err) + } + + ctx.Scale(frames[i], tmp) + + tmp.SetPts(frames[i].Pts()) + tmp.SetPktDts(frames[i].PktDts()) + + result = append(result, tmp) + } + + for i := 0; i < len(frames); i++ { + if frames[i] != nil { + frames[i].Free() + } + } + + return result, nil +}