Remove cgo part
This commit is contained in:
parent
3ddbc8f9d4
commit
83fb5c250e
@ -1,758 +0,0 @@
|
||||
package ffmpeg
|
||||
|
||||
/*
|
||||
#include "ffmpeg.h"
|
||||
int wrap_avcodec_decode_audio4(AVCodecContext *ctx, AVFrame *frame, void *data, int size, int *got) {
|
||||
struct AVPacket pkt = {.data = data, .size = size};
|
||||
return avcodec_decode_audio4(ctx, frame, got, &pkt);
|
||||
}
|
||||
int wrap_avresample_convert(AVAudioResampleContext *avr, int *out, int outsize, int outcount, int *in, int insize, int incount) {
|
||||
return avresample_convert(avr, (void *)out, outsize, outcount, (void *)in, insize, incount);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"runtime"
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/nareix/joy4/av"
|
||||
"github.com/nareix/joy4/av/avutil"
|
||||
"github.com/nareix/joy4/codec/aacparser"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
type Resampler struct {
|
||||
inSampleFormat, OutSampleFormat av.SampleFormat
|
||||
inChannelLayout, OutChannelLayout av.ChannelLayout
|
||||
inSampleRate, OutSampleRate int
|
||||
avr *C.AVAudioResampleContext
|
||||
}
|
||||
|
||||
func (self *Resampler) Resample(in av.AudioFrame) (out av.AudioFrame, err error) {
|
||||
formatChange := in.SampleRate != self.inSampleRate || in.SampleFormat != self.inSampleFormat || in.ChannelLayout != self.inChannelLayout
|
||||
|
||||
var flush av.AudioFrame
|
||||
|
||||
if formatChange {
|
||||
if self.avr != nil {
|
||||
outChannels := self.OutChannelLayout.Count()
|
||||
if !self.OutSampleFormat.IsPlanar() {
|
||||
outChannels = 1
|
||||
}
|
||||
outData := make([]*C.uint8_t, outChannels)
|
||||
outSampleCount := int(C.avresample_get_out_samples(self.avr, C.int(in.SampleCount)))
|
||||
outLinesize := outSampleCount*self.OutSampleFormat.BytesPerSample()
|
||||
flush.Data = make([][]byte, outChannels)
|
||||
for i := 0; i < outChannels; i++ {
|
||||
flush.Data[i] = make([]byte, outLinesize)
|
||||
outData[i] = (*C.uint8_t)(unsafe.Pointer(&flush.Data[i][0]))
|
||||
}
|
||||
flush.ChannelLayout = self.OutChannelLayout
|
||||
flush.SampleFormat = self.OutSampleFormat
|
||||
flush.SampleRate = self.OutSampleRate
|
||||
|
||||
convertSamples := int(C.wrap_avresample_convert(
|
||||
self.avr,
|
||||
(*C.int)(unsafe.Pointer(&outData[0])), C.int(outLinesize), C.int(outSampleCount),
|
||||
nil, C.int(0), C.int(0),
|
||||
))
|
||||
if convertSamples < 0 {
|
||||
err = fmt.Errorf("ffmpeg: avresample_convert_frame failed")
|
||||
return
|
||||
}
|
||||
flush.SampleCount = convertSamples
|
||||
if convertSamples < outSampleCount {
|
||||
for i := 0; i < outChannels; i++ {
|
||||
flush.Data[i] = flush.Data[i][:convertSamples*self.OutSampleFormat.BytesPerSample()]
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Println("flush:", "outSampleCount", outSampleCount, "convertSamples", convertSamples, "datasize", len(flush.Data[0]))
|
||||
} else {
|
||||
runtime.SetFinalizer(self, func(self *Resampler) {
|
||||
self.Close()
|
||||
})
|
||||
}
|
||||
|
||||
C.avresample_free(&self.avr)
|
||||
self.inSampleFormat = in.SampleFormat
|
||||
self.inSampleRate = in.SampleRate
|
||||
self.inChannelLayout = in.ChannelLayout
|
||||
avr := C.avresample_alloc_context()
|
||||
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("in_channel_layout"), C.int64_t(channelLayoutAV2FF(self.inChannelLayout)), 0)
|
||||
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("out_channel_layout"), C.int64_t(channelLayoutAV2FF(self.OutChannelLayout)), 0)
|
||||
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("in_sample_rate"), C.int64_t(self.inSampleRate), 0)
|
||||
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("out_sample_rate"), C.int64_t(self.OutSampleRate), 0)
|
||||
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("in_sample_fmt"), C.int64_t(sampleFormatAV2FF(self.inSampleFormat)), 0)
|
||||
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("out_sample_fmt"), C.int64_t(sampleFormatAV2FF(self.OutSampleFormat)), 0)
|
||||
C.avresample_open(avr)
|
||||
self.avr = avr
|
||||
}
|
||||
|
||||
var inChannels, inLinesize int
|
||||
inSampleCount := in.SampleCount
|
||||
if !self.inSampleFormat.IsPlanar() {
|
||||
inChannels = 1
|
||||
inLinesize = inSampleCount*in.SampleFormat.BytesPerSample()*self.inChannelLayout.Count()
|
||||
} else {
|
||||
inChannels = self.inChannelLayout.Count()
|
||||
inLinesize = inSampleCount*in.SampleFormat.BytesPerSample()
|
||||
}
|
||||
inData := make([]*C.uint8_t, inChannels)
|
||||
for i := 0; i < inChannels; i++ {
|
||||
inData[i] = (*C.uint8_t)(unsafe.Pointer(&in.Data[i][0]))
|
||||
}
|
||||
|
||||
var outChannels, outLinesize, outBytesPerSample int
|
||||
outSampleCount := int(C.avresample_get_out_samples(self.avr, C.int(in.SampleCount)))
|
||||
if !self.OutSampleFormat.IsPlanar() {
|
||||
outChannels = 1
|
||||
outBytesPerSample = self.OutSampleFormat.BytesPerSample()*self.OutChannelLayout.Count()
|
||||
outLinesize = outSampleCount*outBytesPerSample
|
||||
} else {
|
||||
outChannels = self.OutChannelLayout.Count()
|
||||
outBytesPerSample = self.OutSampleFormat.BytesPerSample()
|
||||
outLinesize = outSampleCount*outBytesPerSample
|
||||
}
|
||||
outData := make([]*C.uint8_t, outChannels)
|
||||
out.Data = make([][]byte, outChannels)
|
||||
for i := 0; i < outChannels; i++ {
|
||||
out.Data[i] = make([]byte, outLinesize)
|
||||
outData[i] = (*C.uint8_t)(unsafe.Pointer(&out.Data[i][0]))
|
||||
}
|
||||
out.ChannelLayout = self.OutChannelLayout
|
||||
out.SampleFormat = self.OutSampleFormat
|
||||
out.SampleRate = self.OutSampleRate
|
||||
|
||||
convertSamples := int(C.wrap_avresample_convert(
|
||||
self.avr,
|
||||
(*C.int)(unsafe.Pointer(&outData[0])), C.int(outLinesize), C.int(outSampleCount),
|
||||
(*C.int)(unsafe.Pointer(&inData[0])), C.int(inLinesize), C.int(inSampleCount),
|
||||
))
|
||||
if convertSamples < 0 {
|
||||
err = fmt.Errorf("ffmpeg: avresample_convert_frame failed")
|
||||
return
|
||||
}
|
||||
|
||||
out.SampleCount = convertSamples
|
||||
if convertSamples < outSampleCount {
|
||||
for i := 0; i < outChannels; i++ {
|
||||
out.Data[i] = out.Data[i][:convertSamples*outBytesPerSample]
|
||||
}
|
||||
}
|
||||
|
||||
if flush.SampleCount > 0 {
|
||||
out = flush.Concat(out)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Resampler) Close() {
|
||||
C.avresample_free(&self.avr)
|
||||
}
|
||||
|
||||
type AudioEncoder struct {
|
||||
ff *ffctx
|
||||
SampleRate int
|
||||
Bitrate int
|
||||
ChannelLayout av.ChannelLayout
|
||||
SampleFormat av.SampleFormat
|
||||
FrameSampleCount int
|
||||
framebuf av.AudioFrame
|
||||
codecData av.AudioCodecData
|
||||
resampler *Resampler
|
||||
}
|
||||
|
||||
func sampleFormatAV2FF(sampleFormat av.SampleFormat) (ffsamplefmt int32) {
|
||||
switch sampleFormat {
|
||||
case av.U8:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_U8
|
||||
case av.S16:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_S16
|
||||
case av.S32:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_S32
|
||||
case av.FLT:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_FLT
|
||||
case av.DBL:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_DBL
|
||||
case av.U8P:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_U8P
|
||||
case av.S16P:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_S16P
|
||||
case av.S32P:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_S32P
|
||||
case av.FLTP:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_FLTP
|
||||
case av.DBLP:
|
||||
ffsamplefmt = C.AV_SAMPLE_FMT_DBLP
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sampleFormatFF2AV(ffsamplefmt int32) (sampleFormat av.SampleFormat) {
|
||||
switch ffsamplefmt {
|
||||
case C.AV_SAMPLE_FMT_U8: ///< unsigned 8 bits
|
||||
sampleFormat = av.U8
|
||||
case C.AV_SAMPLE_FMT_S16: ///< signed 16 bits
|
||||
sampleFormat = av.S16
|
||||
case C.AV_SAMPLE_FMT_S32: ///< signed 32 bits
|
||||
sampleFormat = av.S32
|
||||
case C.AV_SAMPLE_FMT_FLT: ///< float
|
||||
sampleFormat = av.FLT
|
||||
case C.AV_SAMPLE_FMT_DBL: ///< double
|
||||
sampleFormat = av.DBL
|
||||
case C.AV_SAMPLE_FMT_U8P: ///< unsigned 8 bits, planar
|
||||
sampleFormat = av.U8P
|
||||
case C.AV_SAMPLE_FMT_S16P: ///< signed 16 bits, planar
|
||||
sampleFormat = av.S16P
|
||||
case C.AV_SAMPLE_FMT_S32P: ///< signed 32 bits, planar
|
||||
sampleFormat = av.S32P
|
||||
case C.AV_SAMPLE_FMT_FLTP: ///< float, planar
|
||||
sampleFormat = av.FLTP
|
||||
case C.AV_SAMPLE_FMT_DBLP: ///< double, planar
|
||||
sampleFormat = av.DBLP
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) SetSampleFormat(fmt av.SampleFormat) (err error) {
|
||||
self.SampleFormat = fmt
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) SetSampleRate(rate int) (err error) {
|
||||
self.SampleRate = rate
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) SetChannelLayout(ch av.ChannelLayout) (err error) {
|
||||
self.ChannelLayout = ch
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) SetBitrate(bitrate int) (err error) {
|
||||
self.Bitrate = bitrate
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) SetOption(key string, val interface{}) (err error) {
|
||||
ff := &self.ff.ff
|
||||
|
||||
sval := fmt.Sprint(val)
|
||||
if key == "profile" {
|
||||
ff.profile = C.avcodec_profile_name_to_int(ff.codec, C.CString(sval))
|
||||
if ff.profile == C.FF_PROFILE_UNKNOWN {
|
||||
err = fmt.Errorf("ffmpeg: profile `%s` invalid", sval)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
C.av_dict_set(&ff.options, C.CString(key), C.CString(sval), 0)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) GetOption(key string, val interface{}) (err error) {
|
||||
ff := &self.ff.ff
|
||||
entry := C.av_dict_get(ff.options, C.CString(key), nil, 0)
|
||||
if entry == nil {
|
||||
err = fmt.Errorf("ffmpeg: GetOption failed: `%s` not exists", key)
|
||||
return
|
||||
}
|
||||
switch p := val.(type) {
|
||||
case *string:
|
||||
*p = C.GoString(entry.value)
|
||||
case *int:
|
||||
fmt.Sscanf(C.GoString(entry.value), "%d", p)
|
||||
default:
|
||||
err = fmt.Errorf("ffmpeg: GetOption failed: val must be *string or *int receiver")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) Setup() (err error) {
|
||||
ff := &self.ff.ff
|
||||
|
||||
ff.frame = C.av_frame_alloc()
|
||||
|
||||
if self.SampleFormat == av.SampleFormat(0) {
|
||||
self.SampleFormat = sampleFormatFF2AV(*ff.codec.sample_fmts)
|
||||
}
|
||||
|
||||
//if self.Bitrate == 0 {
|
||||
// self.Bitrate = 80000
|
||||
//}
|
||||
if self.SampleRate == 0 {
|
||||
self.SampleRate = 44100
|
||||
}
|
||||
if self.ChannelLayout == av.ChannelLayout(0) {
|
||||
self.ChannelLayout = av.CH_STEREO
|
||||
}
|
||||
|
||||
ff.codecCtx.sample_fmt = sampleFormatAV2FF(self.SampleFormat)
|
||||
ff.codecCtx.sample_rate = C.int(self.SampleRate)
|
||||
ff.codecCtx.bit_rate = C.int64_t(self.Bitrate)
|
||||
ff.codecCtx.channel_layout = channelLayoutAV2FF(self.ChannelLayout)
|
||||
ff.codecCtx.strict_std_compliance = C.FF_COMPLIANCE_EXPERIMENTAL
|
||||
ff.codecCtx.flags = C.AV_CODEC_FLAG_GLOBAL_HEADER
|
||||
ff.codecCtx.profile = ff.profile
|
||||
|
||||
if C.avcodec_open2(ff.codecCtx, ff.codec, nil) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: encoder: avcodec_open2 failed")
|
||||
return
|
||||
}
|
||||
self.SampleFormat = sampleFormatFF2AV(ff.codecCtx.sample_fmt)
|
||||
self.FrameSampleCount = int(ff.codecCtx.frame_size)
|
||||
|
||||
extradata := C.GoBytes(unsafe.Pointer(ff.codecCtx.extradata), ff.codecCtx.extradata_size)
|
||||
|
||||
switch ff.codecCtx.codec_id {
|
||||
case C.AV_CODEC_ID_AAC:
|
||||
if self.codecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(extradata); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
self.codecData = audioCodecData{
|
||||
channelLayout: self.ChannelLayout,
|
||||
sampleFormat: self.SampleFormat,
|
||||
sampleRate: self.SampleRate,
|
||||
codecId: ff.codecCtx.codec_id,
|
||||
extradata: extradata,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) prepare() (err error) {
|
||||
ff := &self.ff.ff
|
||||
|
||||
if ff.frame == nil {
|
||||
if err = self.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) CodecData() (codec av.AudioCodecData, err error) {
|
||||
if err = self.prepare(); err != nil {
|
||||
return
|
||||
}
|
||||
codec = self.codecData
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) encodeOne(frame av.AudioFrame) (gotpkt bool, pkt []byte, err error) {
|
||||
if err = self.prepare(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ff := &self.ff.ff
|
||||
|
||||
cpkt := C.AVPacket{}
|
||||
cgotpkt := C.int(0)
|
||||
audioFrameAssignToFF(frame, ff.frame)
|
||||
|
||||
if false {
|
||||
farr := []string{}
|
||||
for i := 0; i < len(frame.Data[0])/4; i++ {
|
||||
var f *float64 = (*float64)(unsafe.Pointer(&frame.Data[0][i*4]))
|
||||
farr = append(farr, fmt.Sprintf("%.8f", *f))
|
||||
}
|
||||
fmt.Println(farr)
|
||||
}
|
||||
cerr := C.avcodec_encode_audio2(ff.codecCtx, &cpkt, ff.frame, &cgotpkt)
|
||||
if cerr < C.int(0) {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_encode_audio2 failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
if cgotpkt != 0 {
|
||||
gotpkt = true
|
||||
pkt = C.GoBytes(unsafe.Pointer(cpkt.data), cpkt.size)
|
||||
C.av_packet_unref(&cpkt)
|
||||
|
||||
if debug {
|
||||
fmt.Println("ffmpeg: Encode", frame.SampleCount, frame.SampleRate, frame.ChannelLayout, frame.SampleFormat, "len", len(pkt))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) resample(in av.AudioFrame) (out av.AudioFrame, err error) {
|
||||
if self.resampler == nil {
|
||||
self.resampler = &Resampler{
|
||||
OutSampleFormat: self.SampleFormat,
|
||||
OutSampleRate: self.SampleRate,
|
||||
OutChannelLayout: self.ChannelLayout,
|
||||
}
|
||||
}
|
||||
if out, err = self.resampler.Resample(in); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) Encode(frame av.AudioFrame) (pkts [][]byte, err error) {
|
||||
var gotpkt bool
|
||||
var pkt []byte
|
||||
|
||||
if frame.SampleFormat != self.SampleFormat || frame.ChannelLayout != self.ChannelLayout || frame.SampleRate != self.SampleRate {
|
||||
if frame, err = self.resample(frame); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if self.FrameSampleCount != 0 {
|
||||
if self.framebuf.SampleCount == 0 {
|
||||
self.framebuf = frame
|
||||
} else {
|
||||
self.framebuf = self.framebuf.Concat(frame)
|
||||
}
|
||||
for self.framebuf.SampleCount >= self.FrameSampleCount {
|
||||
frame := self.framebuf.Slice(0, self.FrameSampleCount)
|
||||
if gotpkt, pkt, err = self.encodeOne(frame); err != nil {
|
||||
return
|
||||
}
|
||||
if gotpkt {
|
||||
pkts = append(pkts, pkt)
|
||||
}
|
||||
self.framebuf = self.framebuf.Slice(self.FrameSampleCount, self.framebuf.SampleCount)
|
||||
}
|
||||
} else {
|
||||
if gotpkt, pkt, err = self.encodeOne(frame); err != nil {
|
||||
return
|
||||
}
|
||||
if gotpkt {
|
||||
pkts = append(pkts, pkt)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioEncoder) Close() {
|
||||
freeFFCtx(self.ff)
|
||||
if self.resampler != nil {
|
||||
self.resampler.Close()
|
||||
self.resampler = nil
|
||||
}
|
||||
}
|
||||
|
||||
func audioFrameAssignToAVParams(f *C.AVFrame, frame *av.AudioFrame) {
|
||||
frame.SampleFormat = sampleFormatFF2AV(int32(f.format))
|
||||
frame.ChannelLayout = channelLayoutFF2AV(f.channel_layout)
|
||||
frame.SampleRate = int(f.sample_rate)
|
||||
}
|
||||
|
||||
func audioFrameAssignToAVData(f *C.AVFrame, frame *av.AudioFrame) {
|
||||
frame.SampleCount = int(f.nb_samples)
|
||||
frame.Data = make([][]byte, int(f.channels))
|
||||
for i := 0; i < int(f.channels); i++ {
|
||||
frame.Data[i] = C.GoBytes(unsafe.Pointer(f.data[i]), f.linesize[0])
|
||||
}
|
||||
}
|
||||
|
||||
func audioFrameAssignToAV(f *C.AVFrame, frame *av.AudioFrame) {
|
||||
audioFrameAssignToAVParams(f, frame)
|
||||
audioFrameAssignToAVData(f, frame)
|
||||
}
|
||||
|
||||
func audioFrameAssignToFFParams(frame av.AudioFrame, f *C.AVFrame) {
|
||||
f.format = C.int(sampleFormatAV2FF(frame.SampleFormat))
|
||||
f.channel_layout = channelLayoutAV2FF(frame.ChannelLayout)
|
||||
f.sample_rate = C.int(frame.SampleRate)
|
||||
f.channels = C.int(frame.ChannelLayout.Count())
|
||||
}
|
||||
|
||||
func audioFrameAssignToFFData(frame av.AudioFrame, f *C.AVFrame) {
|
||||
f.nb_samples = C.int(frame.SampleCount)
|
||||
for i := range frame.Data {
|
||||
f.data[i] = (*C.uint8_t)(unsafe.Pointer(&frame.Data[i][0]))
|
||||
f.linesize[i] = C.int(len(frame.Data[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func audioFrameAssignToFF(frame av.AudioFrame, f *C.AVFrame) {
|
||||
audioFrameAssignToFFParams(frame, f)
|
||||
audioFrameAssignToFFData(frame, f)
|
||||
}
|
||||
|
||||
func channelLayoutFF2AV(layout C.uint64_t) (channelLayout av.ChannelLayout) {
|
||||
if layout & C.AV_CH_FRONT_CENTER != 0 {
|
||||
channelLayout |= av.CH_FRONT_CENTER
|
||||
}
|
||||
if layout & C.AV_CH_FRONT_LEFT != 0 {
|
||||
channelLayout |= av.CH_FRONT_LEFT
|
||||
}
|
||||
if layout & C.AV_CH_FRONT_RIGHT != 0 {
|
||||
channelLayout |= av.CH_FRONT_RIGHT
|
||||
}
|
||||
if layout & C.AV_CH_BACK_CENTER != 0 {
|
||||
channelLayout |= av.CH_BACK_CENTER
|
||||
}
|
||||
if layout & C.AV_CH_BACK_LEFT != 0 {
|
||||
channelLayout |= av.CH_BACK_LEFT
|
||||
}
|
||||
if layout & C.AV_CH_BACK_RIGHT != 0 {
|
||||
channelLayout |= av.CH_BACK_RIGHT
|
||||
}
|
||||
if layout & C.AV_CH_SIDE_LEFT != 0 {
|
||||
channelLayout |= av.CH_SIDE_LEFT
|
||||
}
|
||||
if layout & C.AV_CH_SIDE_RIGHT != 0 {
|
||||
channelLayout |= av.CH_SIDE_RIGHT
|
||||
}
|
||||
if layout & C.AV_CH_LOW_FREQUENCY != 0 {
|
||||
channelLayout |= av.CH_LOW_FREQ
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func channelLayoutAV2FF(channelLayout av.ChannelLayout) (layout C.uint64_t) {
|
||||
if channelLayout & av.CH_FRONT_CENTER != 0 {
|
||||
layout |= C.AV_CH_FRONT_CENTER
|
||||
}
|
||||
if channelLayout & av.CH_FRONT_LEFT != 0 {
|
||||
layout |= C.AV_CH_FRONT_LEFT
|
||||
}
|
||||
if channelLayout & av.CH_FRONT_RIGHT != 0 {
|
||||
layout |= C.AV_CH_FRONT_RIGHT
|
||||
}
|
||||
if channelLayout & av.CH_BACK_CENTER != 0 {
|
||||
layout |= C.AV_CH_BACK_CENTER
|
||||
}
|
||||
if channelLayout & av.CH_BACK_LEFT != 0 {
|
||||
layout |= C.AV_CH_BACK_LEFT
|
||||
}
|
||||
if channelLayout & av.CH_BACK_RIGHT != 0 {
|
||||
layout |= C.AV_CH_BACK_RIGHT
|
||||
}
|
||||
if channelLayout & av.CH_SIDE_LEFT != 0 {
|
||||
layout |= C.AV_CH_SIDE_LEFT
|
||||
}
|
||||
if channelLayout & av.CH_SIDE_RIGHT != 0 {
|
||||
layout |= C.AV_CH_SIDE_RIGHT
|
||||
}
|
||||
if channelLayout & av.CH_LOW_FREQ != 0 {
|
||||
layout |= C.AV_CH_LOW_FREQUENCY
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type AudioDecoder struct {
|
||||
ff *ffctx
|
||||
ChannelLayout av.ChannelLayout
|
||||
SampleFormat av.SampleFormat
|
||||
SampleRate int
|
||||
Extradata []byte
|
||||
}
|
||||
|
||||
func (self *AudioDecoder) Setup() (err error) {
|
||||
ff := &self.ff.ff
|
||||
|
||||
ff.frame = C.av_frame_alloc()
|
||||
|
||||
if len(self.Extradata) > 0 {
|
||||
ff.codecCtx.extradata = (*C.uint8_t)(unsafe.Pointer(&self.Extradata[0]))
|
||||
ff.codecCtx.extradata_size = C.int(len(self.Extradata))
|
||||
}
|
||||
if debug {
|
||||
fmt.Println("ffmpeg: Decoder.Setup Extradata.len", len(self.Extradata))
|
||||
}
|
||||
|
||||
ff.codecCtx.sample_rate = C.int(self.SampleRate)
|
||||
ff.codecCtx.channel_layout = channelLayoutAV2FF(self.ChannelLayout)
|
||||
ff.codecCtx.channels = C.int(self.ChannelLayout.Count())
|
||||
if C.avcodec_open2(ff.codecCtx, ff.codec, nil) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: decoder: avcodec_open2 failed")
|
||||
return
|
||||
}
|
||||
self.SampleFormat = sampleFormatFF2AV(ff.codecCtx.sample_fmt)
|
||||
self.ChannelLayout = channelLayoutFF2AV(ff.codecCtx.channel_layout)
|
||||
if self.SampleRate == 0 {
|
||||
self.SampleRate = int(ff.codecCtx.sample_rate)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioDecoder) Decode(pkt []byte) (gotframe bool, frame av.AudioFrame, err error) {
|
||||
ff := &self.ff.ff
|
||||
|
||||
cgotframe := C.int(0)
|
||||
cerr := C.wrap_avcodec_decode_audio4(ff.codecCtx, ff.frame, unsafe.Pointer(&pkt[0]), C.int(len(pkt)), &cgotframe)
|
||||
if cerr < C.int(0) {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_decode_audio4 failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
if cgotframe != C.int(0) {
|
||||
gotframe = true
|
||||
audioFrameAssignToAV(ff.frame, &frame)
|
||||
frame.SampleRate = self.SampleRate
|
||||
|
||||
if debug {
|
||||
fmt.Println("ffmpeg: Decode", frame.SampleCount, frame.SampleRate, frame.ChannelLayout, frame.SampleFormat)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AudioDecoder) Close() {
|
||||
freeFFCtx(self.ff)
|
||||
}
|
||||
|
||||
func NewAudioEncoderByCodecType(typ av.CodecType) (enc *AudioEncoder, err error) {
|
||||
var id uint32
|
||||
|
||||
switch typ {
|
||||
case av.AAC:
|
||||
id = C.AV_CODEC_ID_AAC
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("ffmpeg: cannot find encoder codecType=%d", typ)
|
||||
return
|
||||
}
|
||||
|
||||
codec := C.avcodec_find_encoder(id)
|
||||
if codec == nil || C.avcodec_get_type(id) != C.AVMEDIA_TYPE_AUDIO {
|
||||
err = fmt.Errorf("ffmpeg: cannot find audio encoder codecId=%d", id)
|
||||
return
|
||||
}
|
||||
|
||||
_enc := &AudioEncoder{}
|
||||
if _enc.ff, err = newFFCtxByCodec(codec); err != nil {
|
||||
return
|
||||
}
|
||||
enc = _enc
|
||||
return
|
||||
}
|
||||
|
||||
func NewAudioEncoderByName(name string) (enc *AudioEncoder, err error) {
|
||||
_enc := &AudioEncoder{}
|
||||
|
||||
codec := C.avcodec_find_encoder_by_name(C.CString(name))
|
||||
if codec == nil || C.avcodec_get_type(codec.id) != C.AVMEDIA_TYPE_AUDIO {
|
||||
err = fmt.Errorf("ffmpeg: cannot find audio encoder name=%s", name)
|
||||
return
|
||||
}
|
||||
|
||||
if _enc.ff, err = newFFCtxByCodec(codec); err != nil {
|
||||
return
|
||||
}
|
||||
enc = _enc
|
||||
return
|
||||
}
|
||||
|
||||
func NewAudioDecoder(codec av.AudioCodecData) (dec *AudioDecoder, err error) {
|
||||
_dec := &AudioDecoder{}
|
||||
var id uint32
|
||||
|
||||
switch codec.Type() {
|
||||
case av.AAC:
|
||||
if aaccodec, ok := codec.(aacparser.CodecData); ok {
|
||||
_dec.Extradata = aaccodec.MPEG4AudioConfigBytes()
|
||||
id = C.AV_CODEC_ID_AAC
|
||||
} else {
|
||||
err = fmt.Errorf("ffmpeg: aac CodecData must be aacparser.CodecData")
|
||||
return
|
||||
}
|
||||
|
||||
case av.SPEEX:
|
||||
id = C.AV_CODEC_ID_SPEEX
|
||||
|
||||
case av.PCM_MULAW:
|
||||
id = C.AV_CODEC_ID_PCM_MULAW
|
||||
|
||||
case av.PCM_ALAW:
|
||||
id = C.AV_CODEC_ID_PCM_ALAW
|
||||
|
||||
default:
|
||||
if ffcodec, ok := codec.(audioCodecData); ok {
|
||||
_dec.Extradata = ffcodec.extradata
|
||||
id = ffcodec.codecId
|
||||
} else {
|
||||
err = fmt.Errorf("ffmpeg: invalid CodecData for ffmpeg to decode")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c := C.avcodec_find_decoder(id)
|
||||
if c == nil || C.avcodec_get_type(c.id) != C.AVMEDIA_TYPE_AUDIO {
|
||||
err = fmt.Errorf("ffmpeg: cannot find audio decoder id=%d", id)
|
||||
return
|
||||
}
|
||||
|
||||
if _dec.ff, err = newFFCtxByCodec(c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_dec.SampleFormat = codec.SampleFormat()
|
||||
_dec.SampleRate = codec.SampleRate()
|
||||
_dec.ChannelLayout = codec.ChannelLayout()
|
||||
if err = _dec.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dec = _dec
|
||||
return
|
||||
}
|
||||
|
||||
type audioCodecData struct {
|
||||
codecId uint32
|
||||
sampleFormat av.SampleFormat
|
||||
channelLayout av.ChannelLayout
|
||||
sampleRate int
|
||||
extradata []byte
|
||||
}
|
||||
|
||||
func (self audioCodecData) Type() av.CodecType {
|
||||
return av.MakeAudioCodecType(self.codecId)
|
||||
}
|
||||
|
||||
func (self audioCodecData) SampleRate() int {
|
||||
return self.sampleRate
|
||||
}
|
||||
|
||||
func (self audioCodecData) SampleFormat() av.SampleFormat {
|
||||
return self.sampleFormat
|
||||
}
|
||||
|
||||
func (self audioCodecData) ChannelLayout() av.ChannelLayout {
|
||||
return self.channelLayout
|
||||
}
|
||||
|
||||
func (self audioCodecData) PacketDuration(data []byte) (dur time.Duration, err error) {
|
||||
// TODO: implement it: ffmpeg get_audio_frame_duration
|
||||
err = fmt.Errorf("ffmpeg: cannot get packet duration")
|
||||
return
|
||||
}
|
||||
|
||||
func AudioCodecHandler(h *avutil.RegisterHandler) {
|
||||
h.AudioDecoder = func(codec av.AudioCodecData) (av.AudioDecoder, error) {
|
||||
if dec, err := NewAudioDecoder(codec); err != nil {
|
||||
return nil, nil
|
||||
} else {
|
||||
return dec, err
|
||||
}
|
||||
}
|
||||
|
||||
h.AudioEncoder = func(typ av.CodecType) (av.AudioEncoder, error) {
|
||||
if enc, err := NewAudioEncoderByCodecType(typ); err != nil {
|
||||
return nil, nil
|
||||
} else {
|
||||
return enc, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,74 +0,0 @@
|
||||
package ffmpeg
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lavformat -lavutil -lavcodec -lavresample -lswscale
|
||||
#include "ffmpeg.h"
|
||||
void ffinit() {
|
||||
av_register_all();
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
QUIET = int(C.AV_LOG_QUIET)
|
||||
PANIC = int(C.AV_LOG_PANIC)
|
||||
FATAL = int(C.AV_LOG_FATAL)
|
||||
ERROR = int(C.AV_LOG_ERROR)
|
||||
WARNING = int(C.AV_LOG_WARNING)
|
||||
INFO = int(C.AV_LOG_INFO)
|
||||
VERBOSE = int(C.AV_LOG_VERBOSE)
|
||||
DEBUG = int(C.AV_LOG_DEBUG)
|
||||
TRACE = int(C.AV_LOG_TRACE)
|
||||
)
|
||||
|
||||
func HasEncoder(name string) bool {
|
||||
return C.avcodec_find_encoder_by_name(C.CString(name)) != nil
|
||||
}
|
||||
|
||||
func HasDecoder(name string) bool {
|
||||
return C.avcodec_find_decoder_by_name(C.CString(name)) != nil
|
||||
}
|
||||
|
||||
//func EncodersList() []string
|
||||
//func DecodersList() []string
|
||||
|
||||
func SetLogLevel(level int) {
|
||||
C.av_log_set_level(C.int(level))
|
||||
}
|
||||
|
||||
func init() {
|
||||
C.ffinit()
|
||||
}
|
||||
|
||||
type ffctx struct {
|
||||
ff C.FFCtx
|
||||
}
|
||||
|
||||
func newFFCtxByCodec(codec *C.AVCodec) (ff *ffctx, err error) {
|
||||
ff = &ffctx{}
|
||||
ff.ff.codec = codec
|
||||
ff.ff.codecCtx = C.avcodec_alloc_context3(codec)
|
||||
ff.ff.profile = C.FF_PROFILE_UNKNOWN
|
||||
runtime.SetFinalizer(ff, freeFFCtx)
|
||||
return
|
||||
}
|
||||
|
||||
func freeFFCtx(self *ffctx) {
|
||||
ff := &self.ff
|
||||
if ff.frame != nil {
|
||||
C.av_frame_free(&ff.frame)
|
||||
}
|
||||
if ff.codecCtx != nil {
|
||||
C.avcodec_close(ff.codecCtx)
|
||||
C.av_free(unsafe.Pointer(ff.codecCtx))
|
||||
ff.codecCtx = nil
|
||||
}
|
||||
if ff.options != nil {
|
||||
C.av_dict_free(&ff.options)
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavresample/avresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <string.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
typedef struct {
|
||||
AVCodec *codec;
|
||||
AVCodecContext *codecCtx;
|
||||
AVFrame *frame;
|
||||
AVDictionary *options;
|
||||
int profile;
|
||||
} FFCtx;
|
||||
|
||||
static inline int avcodec_profile_name_to_int(AVCodec *codec, const char *name) {
|
||||
const AVProfile *p;
|
||||
for (p = codec->profiles; p != NULL && p->profile != FF_PROFILE_UNKNOWN; p++)
|
||||
if (!strcasecmp(p->name, name))
|
||||
return p->profile;
|
||||
return FF_PROFILE_UNKNOWN;
|
||||
}
|
||||
|
@ -1,124 +0,0 @@
|
||||
package ffmpeg
|
||||
|
||||
/*
|
||||
#include "ffmpeg.h"
|
||||
int wrap_avcodec_decode_video2(AVCodecContext *ctx, AVFrame *frame, void *data, int size, int *got) {
|
||||
struct AVPacket pkt = {.data = data, .size = size};
|
||||
return avcodec_decode_video2(ctx, frame, got, &pkt);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"runtime"
|
||||
"fmt"
|
||||
"image"
|
||||
"reflect"
|
||||
"github.com/nareix/joy4/av"
|
||||
"github.com/nareix/joy4/codec/h264parser"
|
||||
)
|
||||
|
||||
type VideoDecoder struct {
|
||||
ff *ffctx
|
||||
Extradata []byte
|
||||
}
|
||||
|
||||
func (self *VideoDecoder) Setup() (err error) {
|
||||
ff := &self.ff.ff
|
||||
if len(self.Extradata) > 0 {
|
||||
ff.codecCtx.extradata = (*C.uint8_t)(unsafe.Pointer(&self.Extradata[0]))
|
||||
ff.codecCtx.extradata_size = C.int(len(self.Extradata))
|
||||
}
|
||||
if C.avcodec_open2(ff.codecCtx, ff.codec, nil) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: decoder: avcodec_open2 failed")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func fromCPtr(buf unsafe.Pointer, size int) (ret []uint8) {
|
||||
hdr := (*reflect.SliceHeader)((unsafe.Pointer(&ret)))
|
||||
hdr.Cap = size
|
||||
hdr.Len = size
|
||||
hdr.Data = uintptr(buf)
|
||||
return
|
||||
}
|
||||
|
||||
type VideoFrame struct {
|
||||
Image image.YCbCr
|
||||
frame *C.AVFrame
|
||||
}
|
||||
|
||||
func (self *VideoFrame) Free() {
|
||||
self.Image = image.YCbCr{}
|
||||
C.av_frame_free(&self.frame)
|
||||
}
|
||||
|
||||
func freeVideoFrame(self *VideoFrame) {
|
||||
self.Free()
|
||||
}
|
||||
|
||||
func (self *VideoDecoder) Decode(pkt []byte) (img *VideoFrame, err error) {
|
||||
ff := &self.ff.ff
|
||||
|
||||
cgotimg := C.int(0)
|
||||
frame := C.av_frame_alloc()
|
||||
cerr := C.wrap_avcodec_decode_video2(ff.codecCtx, frame, unsafe.Pointer(&pkt[0]), C.int(len(pkt)), &cgotimg)
|
||||
if cerr < C.int(0) {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_decode_video2 failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
if cgotimg != C.int(0) {
|
||||
w := int(frame.width)
|
||||
h := int(frame.height)
|
||||
ys := int(frame.linesize[0])
|
||||
cs := int(frame.linesize[1])
|
||||
|
||||
img = &VideoFrame{Image: image.YCbCr{
|
||||
Y: fromCPtr(unsafe.Pointer(frame.data[0]), ys*h),
|
||||
Cb: fromCPtr(unsafe.Pointer(frame.data[1]), cs*h/2),
|
||||
Cr: fromCPtr(unsafe.Pointer(frame.data[2]), cs*h/2),
|
||||
YStride: ys,
|
||||
CStride: cs,
|
||||
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
}, frame: frame}
|
||||
runtime.SetFinalizer(img, freeVideoFrame)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewVideoDecoder(stream av.CodecData) (dec *VideoDecoder, err error) {
|
||||
_dec := &VideoDecoder{}
|
||||
var id uint32
|
||||
|
||||
switch stream.Type() {
|
||||
case av.H264:
|
||||
h264 := stream.(h264parser.CodecData)
|
||||
_dec.Extradata = h264.AVCDecoderConfRecordBytes()
|
||||
id = C.AV_CODEC_ID_H264
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("ffmpeg: NewVideoDecoder codec=%v unsupported", stream.Type())
|
||||
return
|
||||
}
|
||||
|
||||
c := C.avcodec_find_decoder(id)
|
||||
if c == nil || C.avcodec_get_type(id) != C.AVMEDIA_TYPE_VIDEO {
|
||||
err = fmt.Errorf("ffmpeg: cannot find video decoder codecId=%d", id)
|
||||
return
|
||||
}
|
||||
|
||||
if _dec.ff, err = newFFCtxByCodec(c); err != nil {
|
||||
return
|
||||
}
|
||||
if err = _dec.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dec = _dec
|
||||
return
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/nareix/joy4/av"
|
||||
"github.com/nareix/joy4/format"
|
||||
"github.com/nareix/joy4/av/avutil"
|
||||
"github.com/nareix/joy4/cgo/ffmpeg"
|
||||
)
|
||||
|
||||
// need ffmpeg installed
|
||||
|
||||
func init() {
|
||||
format.RegisterAll()
|
||||
}
|
||||
|
||||
func main() {
|
||||
file, _ := avutil.Open("projectindex.flv")
|
||||
streams, _ := file.Streams()
|
||||
var dec *ffmpeg.AudioDecoder
|
||||
|
||||
for _, stream := range streams {
|
||||
if stream.Type() == av.AAC {
|
||||
dec, _ = ffmpeg.NewAudioDecoder(stream.(av.AudioCodecData))
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
pkt, _ := file.ReadPacket()
|
||||
if streams[pkt.Idx].Type() == av.AAC {
|
||||
ok, frame, _ := dec.Decode(pkt.Data)
|
||||
if ok {
|
||||
println("decode samples", frame.SampleCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/nareix/joy4/av"
|
||||
"github.com/nareix/joy4/av/transcode"
|
||||
"github.com/nareix/joy4/format"
|
||||
"github.com/nareix/joy4/av/avutil"
|
||||
"github.com/nareix/joy4/format/rtmp"
|
||||
"github.com/nareix/joy4/cgo/ffmpeg"
|
||||
)
|
||||
|
||||
// need ffmpeg with libspeex and libfdkaac installed
|
||||
//
|
||||
// open http://www.wowza.com/resources/4.4.1/examples/WebcamRecording/FlashRTMPPlayer11/player.html
|
||||
// click connect and recored
|
||||
// input camera H264/SPEEX will converted H264/AAC and saved in out.ts
|
||||
|
||||
func init() {
|
||||
format.RegisterAll()
|
||||
}
|
||||
|
||||
func main() {
|
||||
server := &rtmp.Server{}
|
||||
|
||||
server.HandlePublish = func(conn *rtmp.Conn) {
|
||||
file, _ := avutil.Create("out.ts")
|
||||
|
||||
findcodec := func(stream av.AudioCodecData, i int) (need bool, dec av.AudioDecoder, enc av.AudioEncoder, err error) {
|
||||
need = true
|
||||
dec, _ = ffmpeg.NewAudioDecoder(stream)
|
||||
enc, _ = ffmpeg.NewAudioEncoderByName("libfdk_aac")
|
||||
enc.SetSampleRate(48000)
|
||||
enc.SetChannelLayout(av.CH_STEREO)
|
||||
return
|
||||
}
|
||||
|
||||
trans := &transcode.Demuxer{
|
||||
Options: transcode.Options{
|
||||
FindAudioDecoderEncoder: findcodec,
|
||||
},
|
||||
Demuxer: conn,
|
||||
}
|
||||
|
||||
avutil.CopyFile(file, trans)
|
||||
}
|
||||
|
||||
server.ListenAndServe()
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/nareix/joy4/av"
|
||||
"github.com/nareix/joy4/av/transcode"
|
||||
"github.com/nareix/joy4/format"
|
||||
"github.com/nareix/joy4/av/avutil"
|
||||
"github.com/nareix/joy4/cgo/ffmpeg"
|
||||
)
|
||||
|
||||
// need ffmpeg with libfdkaac installed
|
||||
|
||||
func init() {
|
||||
format.RegisterAll()
|
||||
}
|
||||
|
||||
func main() {
|
||||
infile, _ := avutil.Open("speex.flv")
|
||||
|
||||
findcodec := func(stream av.AudioCodecData, i int) (need bool, dec av.AudioDecoder, enc av.AudioEncoder, err error) {
|
||||
need = true
|
||||
dec, _ = ffmpeg.NewAudioDecoder(stream)
|
||||
enc, _ = ffmpeg.NewAudioEncoderByName("libfdk_aac")
|
||||
enc.SetSampleRate(stream.SampleRate())
|
||||
enc.SetChannelLayout(av.CH_STEREO)
|
||||
enc.SetBitrate(12000)
|
||||
enc.SetOption("profile", "HE-AACv2")
|
||||
return
|
||||
}
|
||||
|
||||
trans := &transcode.Demuxer{
|
||||
Options: transcode.Options{
|
||||
FindAudioDecoderEncoder: findcodec,
|
||||
},
|
||||
Demuxer: infile,
|
||||
}
|
||||
|
||||
outfile, _ := avutil.Create("out.ts")
|
||||
avutil.CopyFile(outfile, trans)
|
||||
|
||||
outfile.Close()
|
||||
infile.Close()
|
||||
trans.Close()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user