├── .gitignore ├── README.md ├── audio_codec_context.go ├── audio_codec_context_opt.go ├── audio_frame.go ├── channel_layout.go ├── chroma_location.go ├── codec.go ├── codec_enum.go ├── codec_parametes.go ├── color_primaries.go ├── color_range.go ├── color_space.go ├── color_transfer_characteristic.go ├── errors.go ├── examples ├── demuxing │ └── main.go ├── encoding_audio │ └── main.go ├── encoding_video │ └── main.go ├── encoding_video_extended │ └── main.go └── muxing │ └── main.go ├── field_order.go ├── format.go ├── format_context.go ├── go.mod ├── init.go ├── io_context.go ├── io_context_context_read_write.go ├── media_type.go ├── packet.go ├── pixel_format.go ├── rational.go ├── sample_format.go ├── scale.go ├── stream.go ├── video_codec_context.go ├── video_codec_context_opt.go └── video_frame.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.avi 3 | *.mp[2-4] 4 | *.mpeg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # go-ffmpeg 3 | 4 | Go binding to FFmpeg 5 | 6 | ## Installation 7 | 8 | ### Install ffpeg libraries 9 | 10 | #### Solus 11 | 12 | ```bash 13 | eopkg it pkg-config ffmpeg-devel 14 | ``` 15 | 16 | #### Ubuntu 17 | 18 | ```bash 19 | apt install pkg-config libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev 20 | ``` 21 | 22 | #### Source code 23 | 24 | ```bash 25 | git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg 26 | cd ffmpeg 27 | ./configure 28 | make 29 | make install 30 | ``` 31 | 32 | #### Required versions 33 | 34 | libavcodec >= 58 35 | libavformat >= 58 36 | libavutil >= 56 37 | libswresample >= 3 38 | libswscale >= 5 39 | 40 | ## go-ffmpeg installation 41 | 42 | ```bash 43 | go get github.com/alexdogonin/go-ffmpeg 44 | ``` 45 | 46 | ## Usage 47 | 48 | see [examples](https://github.com/alexdogonin/go-ffmpeg/tree/master/examples) directory 49 | 50 | ## TODO 51 | 52 | - [ ] add minimal tools number sufficient for this works: 53 | - [x] encoding - generate video from image and audio track 54 | - [ ] decoding - generate number of images and audio file from video 55 | - [ ] rescaling video 56 | - [ ] resampling audio 57 | - [ ] convert formats 58 | - [ ] refactor code, reducing type dependencies among themselves 59 | - [ ] restructure code to use go types instead FFmpeg types everywheare it possible (e.g. AVIOContext -> io.Writer) 60 | - [ ] add comprehensive documentation 61 | - [ ] (maybe never) rewrite code with pure go, rejection of cgo 62 | -------------------------------------------------------------------------------- /audio_codec_context.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "math/bits" 10 | "unsafe" 11 | ) 12 | 13 | type AudioCodecContext struct { 14 | context *C.struct_AVCodecContext 15 | 16 | err error 17 | } 18 | 19 | func NewAudioCodecContext(codec *Codec, bitrate int, rate int, fmt SampleFormat, chLayout ChannelLayout) (*AudioCodecContext, error) { 20 | c := C.avcodec_alloc_context3((*C.struct_AVCodec)(codec)) 21 | 22 | c.bit_rate = C.long(bitrate) 23 | c.channels = C.int(bits.OnesCount64(uint64(chLayout))) 24 | c.channel_layout = chLayout.ctype() 25 | c.sample_rate = C.int(rate) 26 | c.sample_fmt = fmt.ctype() 27 | 28 | context := &AudioCodecContext{ 29 | context: c, 30 | } 31 | // for _, opt := range opts { 32 | // opt(context) 33 | // } 34 | 35 | if ok := int(C.avcodec_open2(c, (*C.struct_AVCodec)(codec), nil)) == 0; !ok { 36 | return nil, errors.New("codec open error") 37 | } 38 | 39 | return context, nil 40 | } 41 | 42 | func (context *AudioCodecContext) Release() { 43 | C.avcodec_free_context(&context.context) 44 | } 45 | 46 | func (context *AudioCodecContext) SendFrame(frame *AudioFrame) error { 47 | if context.err != nil { 48 | return context.err 49 | } 50 | 51 | if int(C.avcodec_send_frame(context.context, frame.ctype())) != 0 { 52 | return errors.New("send frame error") 53 | } 54 | 55 | return nil 56 | } 57 | 58 | func (context *AudioCodecContext) ReceivePacket(dest *Packet) bool { 59 | if context.err != nil { 60 | return false 61 | } 62 | 63 | ret := int(C.avcodec_receive_packet(context.context, dest.ctype())) 64 | if ret == -int(C.EAGAIN) || ret == int(C.AVERROR_EOF) { 65 | return false 66 | } 67 | 68 | if ret < 0 { 69 | context.err = fmt.Errorf("error during encoding (code = %q)", ret) 70 | return false 71 | } 72 | 73 | return true 74 | } 75 | 76 | func (context *AudioCodecContext) Err() error { 77 | return context.err 78 | } 79 | 80 | func (context *AudioCodecContext) SamplesPerFrame() int { 81 | return int(context.context.frame_size) 82 | } 83 | 84 | func (context *AudioCodecContext) CodecParameters() *CodecParameters { 85 | parms := &CodecParameters{} 86 | 87 | C.avcodec_parameters_from_context((*C.struct_AVCodecParameters)(unsafe.Pointer(parms)), context.context) 88 | 89 | return parms 90 | } 91 | 92 | func (context *AudioCodecContext) ctype() *C.struct_AVCodecContext { 93 | return (*C.struct_AVCodecContext)(unsafe.Pointer(context)) 94 | } 95 | -------------------------------------------------------------------------------- /audio_codec_context_opt.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | import "C" 4 | 5 | type AudioCodecContextOpt func(*AudioCodecContext) 6 | 7 | func WithAudioBitrate(bitrate int) AudioCodecContextOpt { 8 | return func(context *AudioCodecContext) { 9 | context.context.bit_rate = C.long(bitrate) 10 | } 11 | } 12 | 13 | func WithSampleRate(sampleRate int) AudioCodecContextOpt { 14 | return func(context *AudioCodecContext) { 15 | context.context.sample_rate = C.int(sampleRate) 16 | } 17 | } 18 | 19 | func WithChannelLayout(layout int) AudioCodecContextOpt { 20 | return func(context *AudioCodecContext) { 21 | context.context.channel_layout = C.ulong(layout) 22 | } 23 | } 24 | 25 | func WithChannels(channels int) AudioCodecContextOpt { 26 | return func(context *AudioCodecContext) { 27 | context.context.channels = C.int(channels) 28 | } 29 | } 30 | 31 | func WithSampleFormat(sampleFmt SampleFormat) AudioCodecContextOpt { 32 | return func(context *AudioCodecContext) { 33 | context.context.sample_fmt = sampleFmt.ctype() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /audio_frame.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "errors" 7 | "fmt" 8 | "unsafe" 9 | ) 10 | 11 | type AudioFrame C.struct_Frame 12 | 13 | func NewAudioFrame(samples int, sampleFmt SampleFormat, channelLayout ChannelLayout) (*AudioFrame, error) { 14 | f := C.av_frame_alloc() 15 | f.nb_samples = C.int(samples) 16 | f.format = C.int(sampleFmt.ctype()) 17 | f.channel_layout = channelLayout.ctype() 18 | 19 | frame := (*AudioFrame)(unsafe.Pointer(f)) 20 | if ret := C.av_frame_get_buffer(frame.ctype(), C.int(1) /*alignment*/); ret < 0 { 21 | frame.Release() 22 | return nil, fmt.Errorf("Error allocating avframe buffer. Err: %v", ret) 23 | } 24 | 25 | return frame, nil 26 | } 27 | 28 | func (frame *AudioFrame) Release() { 29 | C.av_frame_free((**C.struct_AVFrame)(unsafe.Pointer(&frame))) 30 | } 31 | 32 | func (frame *AudioFrame) MakeWritable() error { 33 | if 0 != int(C.av_frame_make_writable(frame.ctype())) { 34 | return errors.New("make writable error") 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (frame *AudioFrame) Write(data []byte) (int, error) { 41 | if int(frame.ctype().linesize[0]) < len(data) { 42 | return 0, errors.New("frame buffer less than writable data") 43 | } 44 | 45 | C.memcpy(unsafe.Pointer(frame.ctype().data[0]), unsafe.Pointer(&(data[0])), C.ulong(len(data))) 46 | 47 | return len(data), nil 48 | } 49 | 50 | func (frame *AudioFrame) SetPts(pts int) { 51 | frame.ctype().pts = C.long(pts) 52 | } 53 | 54 | func (frame *AudioFrame) ctype() *C.struct_AVFrame { 55 | return (*C.struct_AVFrame)(unsafe.Pointer(frame)) 56 | } 57 | -------------------------------------------------------------------------------- /channel_layout.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | import "C" 4 | 5 | type Channel uint64 6 | 7 | const ( 8 | ChannelFrontLeft Channel = 0x00000001 9 | ChannelFrontRight Channel = 0x00000002 10 | ChannelFrontCenter Channel = 0x00000004 11 | ChannelLowFrequency Channel = 0x00000008 12 | ChannelBackLeft Channel = 0x00000010 13 | ChannelBackRight Channel = 0x00000020 14 | ChannelFrontLeftOfCenter Channel = 0x00000040 15 | ChannelFrontRightOfCenter Channel = 0x00000080 16 | ChannelBackCenter Channel = 0x00000100 17 | ChannelSideLeft Channel = 0x00000200 18 | ChannelSideRight Channel = 0x00000400 19 | ChannelTopCenter Channel = 0x00000800 20 | ChannelTopFrontLeft Channel = 0x00001000 21 | ChannelTopFrontCenter Channel = 0x00002000 22 | ChannelTopFrontRight Channel = 0x00004000 23 | ChannelTopBackLeft Channel = 0x00008000 24 | ChannelTopBackCenter Channel = 0x00010000 25 | ChannelTopBackRight Channel = 0x00020000 26 | ChannelStereoLeft Channel = 0x20000000 ///< Stereo downmix. 27 | ChannelStereoRight Channel = 0x40000000 ///< See ChannelStereoLeft. 28 | ChannelWideLeft Channel = 0x0000000080000000 29 | ChannelWideRight Channel = 0x0000000100000000 30 | ChannelSurroundDirectLeft Channel = 0x0000000200000000 31 | ChannelSurroundDirectRight Channel = 0x0000000400000000 32 | ChannelLowFrequency2 Channel = 0x0000000800000000 33 | ) 34 | 35 | type ChannelLayout uint64 36 | 37 | const ( 38 | ChannelLayoutMono ChannelLayout = ChannelLayout(ChannelFrontCenter) 39 | ChannelLayoutStereo ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight) 40 | ChannelLayout2Point1 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelLowFrequency) 41 | ChannelLayout2_1 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelBackCenter) 42 | ChannelLayoutSurround ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter) 43 | ChannelLayout3Point1 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelLowFrequency) 44 | ChannelLayout4Point0 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelBackCenter) 45 | ChannelLayout4Point1 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelBackCenter | ChannelLowFrequency) 46 | ChannelLayout2_2 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelSideLeft | ChannelSideRight) 47 | ChannelLayoutQuad ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelBackLeft | ChannelBackRight) 48 | ChannelLayout5Point0 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight) 49 | ChannelLayout5Point1 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelLowFrequency) 50 | ChannelLayout5Point0Back ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelBackLeft | ChannelBackRight) 51 | ChannelLayout5Point1Back ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelBackLeft | ChannelBackRight | ChannelLowFrequency) 52 | ChannelLayout6Point0 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelBackCenter) 53 | ChannelLayout6Point0Front ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelSideLeft | ChannelSideRight | ChannelFrontLeftOfCenter | ChannelFrontRightOfCenter) 54 | ChannelLayoutHexagonal ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelBackLeft | ChannelBackRight | ChannelBackCenter) 55 | ChannelLayout6Point1 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelLowFrequency | ChannelBackCenter) 56 | ChannelLayout6Point1Back ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelBackLeft | ChannelBackRight | ChannelLowFrequency | ChannelBackCenter) 57 | ChannelLayout6Point1Front ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelSideLeft | ChannelSideRight | ChannelFrontLeftOfCenter | ChannelFrontRightOfCenter | ChannelLowFrequency) 58 | ChannelLayout7Point0 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelBackLeft | ChannelBackRight) 59 | ChannelLayout7Point0Front ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelFrontLeftOfCenter | ChannelFrontRightOfCenter) 60 | ChannelLayout7Point1 ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelLowFrequency | ChannelBackLeft | ChannelBackRight) 61 | ChannelLayout7Point1Wide ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelLowFrequency | ChannelFrontLeftOfCenter | ChannelFrontRightOfCenter) 62 | ChannelLayout7Point1WideBack ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelBackLeft | ChannelBackRight | ChannelLowFrequency | ChannelFrontLeftOfCenter | ChannelFrontRightOfCenter) 63 | ChannelLayoutOctagonal ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelBackLeft | ChannelBackCenter | ChannelBackRight) 64 | ChannelLayoutHexadecagonal ChannelLayout = ChannelLayout(ChannelFrontLeft | ChannelFrontRight | ChannelFrontCenter | ChannelSideLeft | ChannelSideRight | ChannelBackLeft | ChannelBackCenter | ChannelBackRight | ChannelWideLeft | ChannelWideRight | ChannelTopBackLeft | ChannelTopBackRight | ChannelTopBackCenter | ChannelTopFrontCenter | ChannelTopFrontLeft | ChannelTopFrontRight) 65 | ChannelLayoutStereoDownmix ChannelLayout = ChannelLayout(ChannelStereoLeft | ChannelStereoRight) 66 | ) 67 | 68 | func (l ChannelLayout) ctype() C.ulong { 69 | return C.ulong(l) 70 | } 71 | -------------------------------------------------------------------------------- /chroma_location.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type ChromaLocation int 7 | 8 | func (l ChromaLocation) ctype() C.enum_AVChromaLocation { 9 | return (C.enum_AVChromaLocation)(l) 10 | } 11 | -------------------------------------------------------------------------------- /codec.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "unsafe" 10 | ) 11 | 12 | type Codec C.struct_AVCodec 13 | 14 | func CodecByName(name string) (*Codec, error) { 15 | codec := (*Codec)(C.avcodec_find_encoder_by_name(C.CString(name))) 16 | 17 | if codec == nil { 18 | return nil, errors.New("init codec error") 19 | } 20 | 21 | return codec, nil 22 | } 23 | 24 | func CodecByID(codecID CodecID) (*Codec, error) { 25 | codec := (*Codec)(C.avcodec_find_encoder((C.enum_AVCodecID)(codecID))) 26 | if codec == nil { 27 | return nil, fmt.Errorf("couldn't find encoder for codec id %d", codecID) 28 | } 29 | 30 | return codec, nil 31 | } 32 | 33 | func (c *Codec) ctype() *C.struct_AVCodec { 34 | return (*C.struct_AVCodec)(unsafe.Pointer(c)) 35 | } 36 | -------------------------------------------------------------------------------- /codec_enum.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type CodecID int 7 | 8 | const ( 9 | CodecIDH261 CodecID = C.AV_CODEC_ID_H261 10 | CodecIDH263 CodecID = C.AV_CODEC_ID_H263 11 | CodecIDMPEG4 CodecID = C.AV_CODEC_ID_MPEG4 12 | CodecIDMp2 CodecID = C.AV_CODEC_ID_MP2 13 | CodecIDMp3 CodecID = C.AV_CODEC_ID_MP3 14 | CodecIDAAC CodecID = C.AV_CODEC_ID_AAC 15 | ) 16 | 17 | func (cid CodecID) ctype() C.enum_AVCodecID { 18 | return (C.enum_AVCodecID)(cid) 19 | } 20 | -------------------------------------------------------------------------------- /codec_parametes.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type CodecParameters struct { 7 | // General type of the encoded data. 8 | CodecType MediaType 9 | // Specific type of the encoded data (the codec used). 10 | CodecID CodecID 11 | // Additional information about the codec (corresponds to the AVI FOURCC). 12 | CodecTag uint32 13 | // Extra binary data needed for initializing the decoder, codec-dependent. 14 | // Must be allocated with av_malloc() and will be freed by 15 | // avcodec_parameters_free(). The allocated size of extradata must be at 16 | // least extradata_size + AV_INPUT_BUFFER_PADDING_SIZE, with the padding 17 | // bytes zeroed. 18 | ExtraData []byte 19 | // - video: the pixel format, the value corresponds to enum AVPixelFormat. 20 | // - audio: the sample format, the value corresponds to enum AVSampleFormat. 21 | Format int 22 | // The average bitrate of the encoded data (in bits per second). 23 | Bitrate int 24 | // The number of bits per sample in the codedwords. 25 | // This is basically the bitrate per sample. It is mandatory for a bunch of 26 | // formats to actually decode them. It's the number of bits for one sample in 27 | // the actual coded bitstream. 28 | // This could be for example 4 for ADPCM 29 | // For PCM formats this matches bits_per_raw_sample 30 | // Can be 0 31 | BitsPerCodedSample int 32 | // This is the number of valid bits in each output sample. If the 33 | // sample format has more bits, the least significant bits are additional 34 | // padding bits, which are always 0. Use right shifts to reduce the sample 35 | // to its actual size. For example, audio formats with 24 bit samples will 36 | // have bits_per_raw_sample set to 24, and format set to AV_SAMPLE_FMT_S32. 37 | // To get the original sample use "(int32_t)sample >> 8"." 38 | // For ADPCM this might be 12 or 16 or similar 39 | // Can be 0 40 | BitsPerRawSample int 41 | // Codec-specific bitstream restrictions that the stream conforms to. 42 | Profile int 43 | Level int 44 | // Video only. The dimensions of the video frame in pixels. 45 | Width, Height int 46 | // Video only. The aspect ratio (width / height) which a single pixel 47 | // should have when displayed. 48 | // When the aspect ratio is unknown / undefined, the numerator should be 49 | // set to 0 (the denominator may have any value). 50 | SampleAspectRatio Rational 51 | // Video only. The order of the fields in interlaced video. 52 | FieldOrder FieldOrder 53 | // Video only. Additional colorspace characteristics. 54 | ColorRange ColorRange 55 | ColorPrimaries ColorPrimaries 56 | ColorTrc ColorTransferCharacteristic 57 | ColorSpace ColorSpace 58 | ChromaLocation ChromaLocation 59 | // Video only. Number of delayed frames. 60 | VideoDelay int 61 | // Audio only. The channel layout bitmask. May be 0 if the channel layout is 62 | // unknown or unspecified, otherwise the number of bits set must be equal to 63 | // the channels field. 64 | ChannelLayout ChannelLayout 65 | // Audio only. The number of audio channels. 66 | Channels int 67 | // Audio only. The number of audio samples per second. 68 | SampleRate int 69 | // Audio only. The number of bytes per coded audio frame, required by some 70 | // formats. 71 | // Corresponds to nBlockAlign in WAVEFORMATEX. 72 | BlockAlign int 73 | // Audio only. Audio frame size, if known. Required by some formats to be static. 74 | FrameSize int 75 | // Audio only. The amount of padding (in samples) inserted by the encoder at 76 | // the beginning of the audio. I.e. this number of leading decoded samples 77 | // must be discarded by the caller to get the original audio without leading 78 | // padding. 79 | InitialPadding int 80 | // Audio only. The amount of padding (in samples) appended by the encoder to 81 | // the end of the audio. I.e. this number of decoded samples must be 82 | // discarded by the caller from the end of the stream to get the original 83 | // audio without any trailing padding. 84 | TrailingPadding int 85 | // Audio only. Number of samples to skip after a discontinuity. 86 | SeekPreroll int 87 | } 88 | 89 | func (parms *CodecParameters) ctype() *C.struct_AVCodecParameters { 90 | p := C.struct_AVCodecParameters{ 91 | codec_type: parms.CodecType.ctype(), 92 | codec_id: parms.CodecID.ctype(), 93 | codec_tag: C.uint32_t(parms.CodecTag), 94 | extradata_size: C.int(len(parms.ExtraData)), 95 | format: C.int(parms.Format), 96 | bit_rate: C.int64_t(parms.Bitrate), 97 | bits_per_coded_sample: C.int(parms.BitsPerCodedSample), 98 | bits_per_raw_sample: C.int(parms.BitsPerRawSample), 99 | profile: C.int(parms.Profile), 100 | level: C.int(parms.Level), 101 | width: C.int(parms.Width), 102 | height: C.int(parms.Height), 103 | sample_aspect_ratio: parms.SampleAspectRatio.ctype(), 104 | field_order: parms.FieldOrder.ctype(), 105 | color_range: parms.ColorRange.ctype(), 106 | color_primaries: parms.ColorPrimaries.ctype(), 107 | color_trc: parms.ColorTrc.ctype(), 108 | color_space: parms.ColorSpace.ctype(), 109 | chroma_location: parms.ChromaLocation.ctype(), 110 | video_delay: C.int(parms.VideoDelay), 111 | channel_layout: parms.ChannelLayout.ctype(), 112 | channels: C.int(parms.Channels), 113 | sample_rate: C.int(parms.SampleRate), 114 | block_align: C.int(parms.BlockAlign), 115 | frame_size: C.int(parms.FrameSize), 116 | initial_padding: C.int(parms.InitialPadding), 117 | trailing_padding: C.int(parms.TrailingPadding), 118 | seek_preroll: C.int(parms.SeekPreroll), 119 | } 120 | 121 | p.extradata = (*C.uchar)(nil) 122 | if l := len(parms.ExtraData); l != 0 { 123 | // TODO probably memory lick here. check it 124 | // should replace to p.extradata = (*C.uchar)(unsafe.Pointer(&parms.ExtraData[0])) 125 | p.extradata = (*C.uchar)(C.CBytes(parms.ExtraData)) 126 | } 127 | 128 | return &p 129 | } 130 | -------------------------------------------------------------------------------- /color_primaries.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type ColorPrimaries int 7 | 8 | const ( 9 | ColorPrimaries_Unspecified = 2 10 | ) 11 | 12 | func (cp ColorPrimaries) ctype() C.enum_AVColorPrimaries { 13 | return (C.enum_AVColorPrimaries)(cp) 14 | } 15 | -------------------------------------------------------------------------------- /color_range.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type ColorRange int 7 | 8 | func (cr ColorRange) ctype() C.enum_AVColorRange { 9 | return (C.enum_AVColorRange)(cr) 10 | } 11 | -------------------------------------------------------------------------------- /color_space.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type ColorSpace int 7 | 8 | const ( 9 | ColorSpace_Unspecified = 2 10 | ) 11 | 12 | func (c ColorSpace) ctype() C.enum_AVColorSpace { 13 | return (C.enum_AVColorSpace)(c) 14 | } 15 | -------------------------------------------------------------------------------- /color_transfer_characteristic.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type ColorTransferCharacteristic int 7 | 8 | const ( 9 | ColorTransferCharacteristic_Unspecified = 2 10 | ) 11 | 12 | func (c ColorTransferCharacteristic) ctype() C.enum_AVColorTransferCharacteristic { 13 | return (C.enum_AVColorTransferCharacteristic)(c) 14 | } 15 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | const ( 10 | bufferSize = 64 11 | ) 12 | 13 | func Error(errCode C.int) error { 14 | return err(errCode) 15 | } 16 | 17 | type err int 18 | 19 | func (err err) Error() string { 20 | var buff [bufferSize]byte 21 | 22 | ret := C.av_strerror(C.int(err), (*C.char)(unsafe.Pointer(&buff[0])), C.ulong(bufferSize)) 23 | if C.int(ret) != 0 { 24 | return "unknown error" 25 | } 26 | 27 | return string(buff[:]) 28 | } 29 | -------------------------------------------------------------------------------- /examples/demuxing/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/alexdogonin/go-ffmpeg" 7 | ) 8 | 9 | func main() { 10 | const ( 11 | inputFilename = "input.mpeg" 12 | outputAudioFilename = "result.mp3" 13 | outputImagePatternFilename = "result%d.jpeg" 14 | ) 15 | 16 | ioContext, err := ffmpeg.NewIOContext(inputFilename) 17 | if err != nil { 18 | log.Fatalf("create io context error, %s", err) 19 | } 20 | defer ioContext.Release() 21 | 22 | inputFormat, err := ffmpeg.ProbeFormat(ioContext) 23 | if err != nil { 24 | log.Fatalf("probe format error, %s", err) 25 | } 26 | 27 | formatContext, err := ffmpeg.NewInputFormatContext(ioContext, inputFormat) 28 | if err != nil { 29 | log.Fatalf("create format context error, %s", err) 30 | } 31 | // defer formatContext.Close() - duplicated ioContext.Release 32 | defer formatContext.Release() 33 | 34 | if !formatContext.StreamExists() { 35 | log.Fatal("streams not found") 36 | } 37 | 38 | streams := formatContext.Streams() 39 | 40 | var videoStream, audioStream *ffmpeg.Stream 41 | for _, s := range streams { 42 | if videoStream != nil && audioStream != nil { 43 | break 44 | } 45 | 46 | p := s.CodecParameters() 47 | 48 | if audioStream == nil && p.CodecType == ffmpeg.MediaTypeAudio { 49 | audioStream = s 50 | continue 51 | } 52 | 53 | if videoStream == nil && p.CodecType == ffmpeg.MediaTypeVideo { 54 | videoStream = s 55 | continue 56 | } 57 | } 58 | 59 | if audioStream == nil { 60 | log.Fatal("audio stream not found") 61 | } 62 | 63 | if videoStream == nil { 64 | log.Fatal("video stream not found") 65 | } 66 | 67 | codecParms := videoStream.CodecParameters() 68 | videoCodec, err := ffmpeg.CodecByID(codecParms.CodecID) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | videoCodecContext, err := ffmpeg.NewVideoCodecContext( 74 | videoCodec, 75 | codecParms.Width, 76 | codecParms.Height, 77 | ffmpeg.PixelFormat(codecParms.Format), 78 | ) 79 | 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | defer videoCodecContext.Release() 84 | 85 | // codecParms = audioStream.CodecParameters() 86 | // audioCodec, err := ffmpeg.CodecByID(codecParms.CodecID) 87 | // if err != nil { 88 | // log.Fatal(err) 89 | // } 90 | 91 | // audioCodecContext, err := ffmpeg.NewAudioCodecContext( 92 | // audioCodec, 93 | // codecParms.Bitrate, 94 | // codecParms.SampleRate, 95 | // ffmpeg.SampleFormat(codecParms.Format), 96 | // codecParms.ChannelLayout, 97 | // ) 98 | // defer audioCodecContext.Release() 99 | 100 | frame, err := ffmpeg.NewVideoFrame(codecParms.Width, codecParms.Height, ffmpeg.PixelFormat(codecParms.Format)) 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | defer frame.Release() 105 | 106 | formatContext.ReadPacket() 107 | } 108 | -------------------------------------------------------------------------------- /examples/encoding_audio/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "log" 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "github.com/alexdogonin/go-ffmpeg" 11 | ) 12 | 13 | func main() { 14 | const ( 15 | filename = "result.mp3" 16 | codecID = ffmpeg.CodecIDMp3 17 | bitrate = 64000 18 | sampleFmt = ffmpeg.SampleFormatS32P 19 | sampleRate = 44100 20 | channelsLayout = ffmpeg.ChannelLayoutMono 21 | duration = 5 * time.Second 22 | ) 23 | 24 | codec, err := ffmpeg.CodecByID(codecID) 25 | if err != nil { 26 | log.Fatal("init codec error") 27 | } 28 | 29 | context, err := ffmpeg.NewAudioCodecContext(codec, bitrate, sampleRate, sampleFmt, channelsLayout) 30 | if err != nil { 31 | log.Fatal("init context error") 32 | } 33 | defer context.Release() 34 | 35 | frame, err := ffmpeg.NewAudioFrame(context.SamplesPerFrame(), sampleFmt, channelsLayout) 36 | if err != nil { 37 | log.Fatal("init frame error") 38 | } 39 | defer frame.Release() 40 | 41 | packet, err := ffmpeg.NewPacket() 42 | if err != nil { 43 | log.Fatal("init packet error") 44 | } 45 | defer packet.Release() 46 | 47 | f, err := os.Create(filename) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | defer f.Close() 52 | 53 | frames := int(duration.Seconds()) * sampleRate / context.SamplesPerFrame() 54 | 55 | data := make([]byte, 0) 56 | 57 | for i := 0; i < frames; i++ { 58 | data = fillFakeFrame(context.SamplesPerFrame(), i, sampleRate, data[:0]) 59 | 60 | if _, err = frame.Write(data); err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | if err = context.SendFrame(frame); err != nil { 65 | log.Fatal(err) 66 | } 67 | 68 | for context.ReceivePacket(packet) { 69 | if _, err := f.Write(packet.Data()); err != nil { 70 | log.Fatalf("write file error, %v", err) 71 | } 72 | 73 | packet.Unref() 74 | } 75 | 76 | if err = context.Err(); err != nil { 77 | log.Fatalf("receive packet error, %s", err) 78 | } 79 | 80 | } 81 | 82 | // flush 83 | if err = context.SendFrame(nil); err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | for context.ReceivePacket(packet) { 88 | if _, err := f.Write(packet.Data()); err != nil { 89 | log.Fatalf("write file error, %v", err) 90 | } 91 | 92 | packet.Unref() 93 | } 94 | 95 | if err = context.Err(); err != nil { 96 | log.Fatalf("receive packet error, %s", err) 97 | } 98 | } 99 | 100 | func fillFakeFrame(samples, i int, sampleRate int, dest []byte) []byte { 101 | timePerSample := 2 * math.Pi * 440 / float64(sampleRate) 102 | 103 | for j := 0; j < samples; j++ { 104 | t := timePerSample * float64(i*samples+j) 105 | 106 | const maxLevel = math.MaxUint32 / 2 107 | level := uint32(math.Sin(t) * maxLevel) 108 | 109 | const formatBytes = 4 110 | bytes := make([]byte, formatBytes) 111 | 112 | binary.LittleEndian.PutUint32(bytes, level) 113 | 114 | dest = append(dest, bytes...) 115 | } 116 | 117 | return dest 118 | } 119 | -------------------------------------------------------------------------------- /examples/encoding_video/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "log" 6 | 7 | "os" 8 | 9 | "github.com/alexdogonin/go-ffmpeg" 10 | ) 11 | 12 | func main() { 13 | const ( 14 | framerate = 25 15 | codecID = ffmpeg.CodecIDMPEG4 16 | resultName = "result.avi" 17 | videoWidth = 704 18 | videoHeight = 576 19 | durationSec = 8 20 | ) 21 | 22 | f, err := os.Create(resultName) 23 | if err != nil { 24 | log.Fatalf("create result file error, %s", err) 25 | } 26 | defer f.Close() 27 | 28 | codec, err := ffmpeg.CodecByID(codecID) 29 | if err != nil { 30 | log.Fatalf("find codec %d error, %s", codecID, err) 31 | } 32 | 33 | codecCtx, err := ffmpeg.NewVideoCodecContext(codec, videoWidth, videoHeight, ffmpeg.YUV420P, 34 | ffmpeg.WithGopSize(10), 35 | ) 36 | if err != nil { 37 | log.Fatalf("initialize codec context error, %s", err) 38 | } 39 | defer codecCtx.Release() 40 | 41 | frame, err := ffmpeg.NewVideoFrame(videoWidth, videoHeight, ffmpeg.YUV420P) 42 | if err != nil { 43 | log.Fatalf("initialize frame error, %s", err) 44 | } 45 | defer frame.Release() 46 | 47 | frameImg := image.NewYCbCr(image.Rect(0, 0, videoWidth, videoHeight), image.YCbCrSubsampleRatio420) 48 | 49 | packet, err := ffmpeg.NewPacket() 50 | if err != nil { 51 | log.Fatalf("initialize packet error, %s", err) 52 | } 53 | defer packet.Release() 54 | 55 | totalFrames := framerate * durationSec 56 | for i := 0; i < totalFrames; i++ { 57 | fillFakeImg(frameImg, i) 58 | 59 | if err = frame.FillYCbCr(frameImg); err != nil { 60 | log.Fatalf("fill frame error, %s", err) 61 | } 62 | 63 | frame.SetPts(i) 64 | 65 | if err = codecCtx.SendFrame(frame); err != nil { 66 | log.Fatalf("send frame to encoding error, %s", err) 67 | } 68 | 69 | for codecCtx.ReceivePacket(packet) { 70 | if _, err := f.Write(packet.Data()); err != nil { 71 | log.Fatalf("write file error, %v", err) 72 | } 73 | 74 | packet.Unref() 75 | } 76 | 77 | if err = codecCtx.Err(); err != nil { 78 | log.Fatalf("receive packet error, %s", err) 79 | } 80 | } 81 | 82 | // flush 83 | if err = codecCtx.SendFrame(nil); err != nil { 84 | log.Fatalf("send frame to encoding error, %s", err) 85 | } 86 | 87 | for codecCtx.ReceivePacket(packet) { 88 | if _, err := f.Write(packet.Data()); err != nil { 89 | log.Fatalf("write file error, %v", err) 90 | } 91 | 92 | packet.Unref() 93 | } 94 | 95 | if err = codecCtx.Err(); err != nil { 96 | log.Fatalf("receive packet error, %s", err) 97 | } 98 | } 99 | 100 | func fillFakeImg(img *image.YCbCr, i int) { 101 | /* prepare a dummy image */ 102 | /* Y */ 103 | for y := 0; y < img.Bounds().Max.Y; y++ { 104 | for x := 0; x < img.Bounds().Max.X; x++ { 105 | img.Y[y*img.YStride+x] = uint8(x + y + i*3) 106 | } 107 | } 108 | 109 | /* Cb and Cr */ 110 | for y := 0; y < img.Bounds().Max.Y/2; y++ { 111 | for x := 0; x < img.Bounds().Max.X/2; x++ { 112 | img.Cb[y*img.CStride+x] = uint8(128 + y + i*2) 113 | img.Cr[y*img.CStride+x] = uint8(64 + x + i*5) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /examples/encoding_video_extended/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "time" 8 | 9 | "image" 10 | 11 | "github.com/alexdogonin/go-ffmpeg" 12 | ) 13 | 14 | func main() { 15 | const ( 16 | pixelFormat = ffmpeg.YUV420P 17 | subsampleRatio = image.YCbCrSubsampleRatio420 18 | width = 640 19 | height = 480 20 | fileName = "result.avi" 21 | duration = 10 * time.Second 22 | defaulFramerate = 25 23 | ) 24 | 25 | format, err := ffmpeg.GuessFileNameFormat(fileName) 26 | if err != nil { 27 | log.Fatalf("init format error, %s", err) 28 | } 29 | 30 | f, err := os.Create(fileName) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | defer f.Close() 35 | 36 | ioContext, err := ffmpeg.NewIOContext(nil, f) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | formatContext, err := ffmpeg.NewOutputFormatContext(ioContext, format) 42 | if err != nil { 43 | log.Fatalf("init format context error, %s", err) 44 | } 45 | defer formatContext.Release() 46 | 47 | codec, err := ffmpeg.CodecByID(format.VideoCodec()) 48 | if err != nil { 49 | log.Fatalf("find encodec error, %s", err) 50 | } 51 | 52 | codecContext, err := ffmpeg.NewVideoCodecContext(codec, width, height, pixelFormat) 53 | if err != nil { 54 | log.Fatalf("init codec context error") 55 | } 56 | defer codecContext.Release() 57 | 58 | stream, err := ffmpeg.NewStream(formatContext) 59 | if err != nil { 60 | log.Fatalf("init stream error, %s", err) 61 | } 62 | 63 | codecParms := codecContext.CodecParameters() 64 | stream.SetCodecParameters(codecParms) 65 | 66 | frame, err := ffmpeg.NewVideoFrame(width, height, pixelFormat) 67 | if err != nil { 68 | log.Fatalf("init frame error, %s", err) 69 | } 70 | defer frame.Release() 71 | 72 | formatContext.DumpFormat() 73 | 74 | if err = formatContext.WriteHeader(nil); err != nil { 75 | log.Fatalf("write header error, %s", err) 76 | } 77 | 78 | packet, err := ffmpeg.NewPacket() 79 | if err != nil { 80 | log.Fatalf("init packet error, %s", err) 81 | } 82 | defer packet.Release() 83 | 84 | img := image.NewYCbCr(image.Rect(0, 0, width, height), subsampleRatio) 85 | // write frame here 86 | framesCount := int(duration.Seconds()) * defaulFramerate 87 | for i := 0; i < framesCount; i++ { 88 | fillFakeImg(img, i) 89 | 90 | if err = frame.FillYCbCr(img); err != nil { 91 | log.Fatalf("fill image error, %s", err) 92 | } 93 | 94 | frame.SetPts(i) 95 | 96 | if err = codecContext.SendFrame(frame); err != nil { 97 | log.Fatalf("encode frame error, %s", err) 98 | } 99 | 100 | for codecContext.ReceivePacket(packet) { 101 | if err = formatContext.WritePacket(packet); err != nil { 102 | log.Fatalf("write packet to output error, %s", err) 103 | } 104 | 105 | packet.Unref() 106 | } 107 | 108 | if err = codecContext.Err(); err != nil { 109 | log.Fatalf("receive packet error, %s", err) 110 | } 111 | } 112 | 113 | // flush 114 | if err = codecContext.SendFrame(nil); err != nil { 115 | log.Fatalf("encode frame error, %s", err) 116 | } 117 | 118 | for codecContext.ReceivePacket(packet) { 119 | if err = formatContext.WritePacket(packet); err != nil { 120 | log.Fatalf("write packet to output error, %s", err) 121 | } 122 | 123 | packet.Unref() 124 | } 125 | 126 | if err = codecContext.Err(); err != nil { 127 | log.Fatalf("receive packet error, %s", err) 128 | } 129 | 130 | if err = formatContext.WriteTrailer(); err != nil { 131 | log.Fatalf("write trailer error, %s", err) 132 | } 133 | } 134 | 135 | func fillFakeImg(img *image.YCbCr, frameInd int) { 136 | /* prepare a dummy image */ 137 | /* Y */ 138 | for y := 0; y < img.Bounds().Max.Y; y++ { 139 | for x := 0; x < img.Bounds().Max.X; x++ { 140 | img.Y[y*img.YStride+x] = uint8(x + y + frameInd*3) 141 | } 142 | } 143 | 144 | /* Cb and Cr */ 145 | for y := 0; y < img.Bounds().Max.Y/2; y++ { 146 | for x := 0; x < img.Bounds().Max.X/2; x++ { 147 | img.Cb[y*img.CStride+x] = uint8(128 + y + frameInd*2) 148 | img.Cr[y*img.CStride+x] = uint8(64 + x + frameInd*5) 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /examples/muxing/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#cgo pkg-config: libavcodec 4 | //#include 5 | //#include 6 | //#include 7 | import "C" 8 | 9 | import ( 10 | "encoding/binary" 11 | "image" 12 | "log" 13 | "math" 14 | "time" 15 | 16 | "github.com/alexdogonin/go-ffmpeg" 17 | ) 18 | 19 | func main() { 20 | const ( 21 | filename = "result.avi" 22 | pixelFormat = ffmpeg.YUV420P 23 | subsampleRatio = image.YCbCrSubsampleRatio420 24 | width = 640 25 | height = 480 26 | framerate = 25 27 | bitrate = 64000 28 | sampleFormat = ffmpeg.SampleFormatS32P 29 | sampleRate = 44100 30 | channelsLayout = ffmpeg.ChannelLayoutMono 31 | duration = 5 * time.Second 32 | ) 33 | 34 | format, err := ffmpeg.GuessFormat("avi") 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | formatContext, err := ffmpeg.NewFormatContext(filename, format) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | defer formatContext.Release() 44 | 45 | videoCodec, err := ffmpeg.CodecByID(format.VideoCodec()) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | 50 | audioCodec, err := ffmpeg.CodecByID(format.AudioCodec()) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | videoCodecContext, err := ffmpeg.NewVideoCodecContext(videoCodec, width, height, pixelFormat) 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | defer videoCodecContext.Release() 60 | 61 | audioCodecContext, err := ffmpeg.NewAudioCodecContext(audioCodec, bitrate, sampleRate, sampleFormat, channelsLayout) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | defer audioCodecContext.Release() 66 | 67 | videoStream, err := ffmpeg.NewStream(formatContext) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | codecParms := videoCodecContext.CodecParameters() 72 | videoStream.SetCodecParameters(codecParms) 73 | 74 | audioStream, err := ffmpeg.NewStream(formatContext) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | codecParms = audioCodecContext.CodecParameters() 79 | audioStream.SetCodecParameters(codecParms) 80 | 81 | if err = formatContext.OpenOutput(); err != nil { 82 | log.Fatalf("open output error, %s", err) 83 | } 84 | defer formatContext.CloseOutput() 85 | 86 | packet, err := ffmpeg.NewPacket() 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | defer packet.Release() 91 | 92 | videoFrame, err := ffmpeg.NewVideoFrame(width, height, pixelFormat) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | defer videoFrame.Release() 97 | 98 | audioFrame, err := ffmpeg.NewAudioFrame(audioCodecContext.SamplesPerFrame(), sampleFormat, channelsLayout) 99 | if err != nil { 100 | log.Fatal(err) 101 | } 102 | defer audioFrame.Release() 103 | 104 | if err = formatContext.WriteHeader(nil); err != nil { 105 | log.Fatalf("write header error, %s", err) 106 | } 107 | 108 | formatContext.DumpFormat() 109 | 110 | videoFramesCount := int(duration.Seconds()) * framerate 111 | img := image.NewYCbCr(image.Rect(0, 0, width, height), subsampleRatio) 112 | 113 | packet.SetStream(0) 114 | 115 | for i := 0; i < videoFramesCount; i++ { 116 | fillFakeImg(img, i) 117 | 118 | if err = videoFrame.FillYCbCr(img); err != nil { 119 | log.Fatalf("fill image error, %s", err) 120 | } 121 | 122 | videoFrame.SetPts(i) 123 | 124 | if err = videoCodecContext.SendFrame(videoFrame); err != nil { 125 | log.Fatalf("encode frame error, %s", err) 126 | } 127 | 128 | for videoCodecContext.ReceivePacket(packet) { 129 | packet.RescaleTimestamp(ffmpeg.Rational{1, 25}, videoStream.TimeBase()) 130 | 131 | if err = formatContext.WritePacket(packet); err != nil { 132 | log.Fatalf("write packet to output error, %s", err) 133 | } 134 | 135 | packet.Unref() 136 | } 137 | 138 | if err = videoCodecContext.Err(); err != nil { 139 | log.Fatalf("receive packet error, %s", err) 140 | } 141 | 142 | } 143 | 144 | if err = videoCodecContext.SendFrame(nil); err != nil { 145 | log.Fatalf("encode frame error, %s", err) 146 | } 147 | 148 | for videoCodecContext.ReceivePacket(packet) { 149 | packet.RescaleTimestamp(ffmpeg.Rational{1, 25}, videoStream.TimeBase()) 150 | 151 | if err = formatContext.WritePacket(packet); err != nil { 152 | log.Fatalf("write packet to output error, %s", err) 153 | } 154 | 155 | packet.Unref() 156 | } 157 | 158 | if err = videoCodecContext.Err(); err != nil { 159 | log.Fatalf("receive packet error, %s", err) 160 | } 161 | 162 | audioFramesCount := int(duration.Seconds()) * sampleRate / audioCodecContext.SamplesPerFrame() 163 | timePerSample := 2 * math.Pi * 440.0 / sampleRate 164 | 165 | for i := 0; i < audioFramesCount; i++ { 166 | data := make([]byte, 0) 167 | 168 | for j := 0; j < audioCodecContext.SamplesPerFrame(); j++ { 169 | t := timePerSample * float64(i*audioCodecContext.SamplesPerFrame()+j) 170 | 171 | const maxLevel = math.MaxUint32 / 2 172 | level := uint32(math.Sin(t) * maxLevel) 173 | 174 | const formatBytes = 4 175 | bytes := make([]byte, formatBytes) 176 | 177 | binary.LittleEndian.PutUint32(bytes, level) 178 | 179 | data = append(data, bytes...) 180 | } 181 | 182 | if _, err = audioFrame.Write(data); err != nil { 183 | log.Fatal(err) 184 | } 185 | 186 | audioFrame.SetPts(i * audioCodecContext.SamplesPerFrame()) 187 | 188 | if err = audioCodecContext.SendFrame(audioFrame); err != nil { 189 | log.Fatal(err) 190 | } 191 | 192 | for audioCodecContext.ReceivePacket(packet) { 193 | packet.SetStream(1) 194 | 195 | packet.RescaleTimestamp(ffmpeg.Rational{1, 44100}, audioStream.TimeBase()) 196 | 197 | if err = formatContext.WritePacket(packet); err != nil { 198 | log.Fatalf("write packet to output error, %s", err) 199 | } 200 | 201 | packet.Unref() 202 | } 203 | 204 | if err = audioCodecContext.Err(); err != nil { 205 | log.Fatalf("receive packet error, %s", err) 206 | } 207 | } 208 | 209 | if err = audioCodecContext.SendFrame(nil); err != nil { 210 | log.Fatal(err) 211 | } 212 | 213 | for audioCodecContext.ReceivePacket(packet) { 214 | packet.SetStream(1) 215 | 216 | packet.RescaleTimestamp(ffmpeg.Rational{1, 44100}, audioStream.TimeBase()) 217 | 218 | if err = formatContext.WritePacket(packet); err != nil { 219 | log.Fatalf("write packet to output error, %s", err) 220 | } 221 | 222 | packet.Unref() 223 | } 224 | 225 | if err = audioCodecContext.Err(); err != nil { 226 | log.Fatalf("receive packet error, %s", err) 227 | } 228 | 229 | if err = formatContext.WriteTrailer(); err != nil { 230 | log.Fatalf("write trailer error, %s", err) 231 | } 232 | 233 | } 234 | 235 | func fillFakeImg(img *image.YCbCr, frameInd int) { 236 | /* prepare a dummy image */ 237 | /* Y */ 238 | for y := 0; y < img.Bounds().Max.Y; y++ { 239 | for x := 0; x < img.Bounds().Max.X; x++ { 240 | img.Y[y*img.YStride+x] = uint8(x + y + frameInd*3) 241 | } 242 | } 243 | 244 | /* Cb and Cr */ 245 | for y := 0; y < img.Bounds().Max.Y/2; y++ { 246 | for x := 0; x < img.Bounds().Max.X/2; x++ { 247 | img.Cb[y*img.CStride+x] = uint8(128 + y + frameInd*2) 248 | img.Cr[y*img.CStride+x] = uint8(64 + x + frameInd*5) 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /field_order.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type FieldOrder int 7 | 8 | func (fo FieldOrder) ctype() C.enum_AVFieldOrder { 9 | return (C.enum_AVFieldOrder)(fo) 10 | } 11 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "unsafe" 10 | ) 11 | 12 | type OutputFormat C.struct_AVOutputFormat 13 | 14 | func GuessFileNameFormat(filename string) (*OutputFormat, error) { 15 | format := (*OutputFormat)(unsafe.Pointer(C.av_guess_format(nil, C.CString(filename), nil))) 16 | 17 | if format == nil { 18 | return nil, errors.New(fmt.Sprintf("format not for filename %q", filename)) 19 | } 20 | 21 | return format, nil 22 | } 23 | 24 | func GuessFormat(name string) (*OutputFormat, error) { 25 | format := (*OutputFormat)(unsafe.Pointer(C.av_guess_format(C.CString(name), nil, nil))) 26 | 27 | if format == nil { 28 | return nil, errors.New(fmt.Sprintf("format %q not found", name)) 29 | } 30 | 31 | return format, nil 32 | } 33 | 34 | func (format *OutputFormat) VideoCodec() CodecID { 35 | return CodecID(format.ctype().video_codec) 36 | } 37 | 38 | func (format *OutputFormat) AudioCodec() CodecID { 39 | return CodecID(format.ctype().audio_codec) 40 | } 41 | 42 | func (format *OutputFormat) ctype() *C.struct_AVOutputFormat { 43 | return (*C.struct_AVOutputFormat)(unsafe.Pointer(format)) 44 | } 45 | 46 | type InputFormat C.struct_AVInputFormat 47 | 48 | func ProbeFormat(ioCtx *IOContext) (*InputFormat, error) { 49 | var inputFmt *InputFormat 50 | 51 | ret := C.av_probe_input_buffer2(ioCtx.ctype(), (**C.struct_AVInputFormat)(unsafe.Pointer(&inputFmt)), (*C.char)(unsafe.Pointer(&[0]byte{})), nil, 0, 0) 52 | if int(ret) < 0 { 53 | return nil, fmt.Errorf("probe input error, %s", Error(ret)) 54 | } 55 | 56 | return inputFmt, nil 57 | } 58 | 59 | func (format *InputFormat) ctype() *C.struct_AVInputFormat { 60 | return (*C.struct_AVInputFormat)(unsafe.Pointer(format)) 61 | } 62 | -------------------------------------------------------------------------------- /format_context.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0}; 10 | 11 | char *av_err(int errnum) { 12 | av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); 13 | return errbuf; 14 | } 15 | */ 16 | import "C" 17 | 18 | import ( 19 | "fmt" 20 | "unsafe" 21 | ) 22 | 23 | const ( 24 | AVFMT_FLAG_CUSTOM_IO = 0x0080 25 | 26 | AV_NOPTS_VALUE = C.ulong(0x8000000000000000) 27 | ) 28 | 29 | type FormatContext C.struct_AVFormatContext 30 | 31 | func NewOutputFormatContext(output *IOContext, oFormat *OutputFormat) (*FormatContext, error) { 32 | context := (*FormatContext)(unsafe.Pointer(C.avformat_alloc_context())) 33 | 34 | // C.av_strlcpy(&(context.ctype().filename[0]), C.CString(filename), C.ulong(len(filename)+1)) 35 | // context.url = C.av_strdup(C.CString(filename)) 36 | 37 | context.oformat = oFormat.ctype() 38 | 39 | context.priv_data = nil 40 | if context.oformat.priv_data_size != C.int(0) { 41 | context.priv_data = C.av_mallocz(C.ulong(context.oformat.priv_data_size)) 42 | 43 | if context.oformat.priv_class != nil { 44 | *((**C.struct_AVClass)(context.priv_data)) = context.oformat.priv_class 45 | 46 | C.av_opt_set_defaults(context.priv_data) 47 | } 48 | } 49 | 50 | context.ctype().pb = output.ctype() 51 | 52 | return context, nil 53 | } 54 | 55 | func NewInputFormatContext(input *IOContext, iFormat *InputFormat) (*FormatContext, error) { 56 | 57 | context := (*FormatContext)(unsafe.Pointer(C.avformat_alloc_context())) 58 | 59 | context.ctype().iformat = iFormat.ctype() 60 | context.ctype().pb = input.ctype() 61 | context.ctype().flags |= AVFMT_FLAG_CUSTOM_IO 62 | 63 | // C.av_strlcpy(&(context.ctype().filename[0]), C.CString(filename), C.ulong(len(filename)+1)) 64 | // context.ctype().url = C.av_strdup(C.CString(filename)) 65 | 66 | v := AV_NOPTS_VALUE 67 | 68 | context.ctype().duration = *(*C.int64_t)(unsafe.Pointer(&v)) 69 | context.ctype().start_time = *(*C.int64_t)(unsafe.Pointer(&v)) 70 | 71 | context.priv_data = nil 72 | if context.iformat.priv_data_size != C.int(0) { 73 | context.priv_data = C.av_mallocz(C.ulong(context.oformat.priv_data_size)) 74 | 75 | if context.oformat.priv_class != nil { 76 | *((**C.struct_AVClass)(context.priv_data)) = context.oformat.priv_class 77 | 78 | C.av_opt_set_defaults(context.priv_data) 79 | } 80 | } 81 | 82 | //call static int update_stream_avctx(AVFormatContext *s) 83 | 84 | panic("not implemented") 85 | return nil, nil 86 | } 87 | 88 | func (context *FormatContext) StreamExists() bool { 89 | return C.avformat_find_stream_info(context.ctype(), nil) >= 0 90 | } 91 | 92 | func (context *FormatContext) Release() { 93 | C.avformat_free_context(context.ctype()) 94 | } 95 | 96 | func (context *FormatContext) DumpFormat() { 97 | C.av_dump_format(context.ctype(), 0, &(context.ctype().filename[0]), 1) 98 | } 99 | 100 | // func (context *FormatContext) OpenIO() error { 101 | // if (context.ctype().oformat.flags & C.AVFMT_NOFILE) != 0 { 102 | // return nil 103 | // } 104 | 105 | // ret := C.avio_open(&(context.ctype().pb), context.ctype().url, C.AVIO_FLAG_WRITE) 106 | // if ret < 0 { 107 | // return fmt.Errorf("open %q error, %s", context.ctype().filename, C.av_err(C.int(ret))) 108 | // } 109 | 110 | // return nil 111 | // } 112 | 113 | // func (context *FormatContext) Close() { 114 | // C.avio_closep(&(context.ctype().pb)) 115 | // } 116 | 117 | func (context *FormatContext) WriteHeader(opts map[string]string) error { 118 | // var opt C.struct_AVDictionary 119 | // p := &opt 120 | 121 | // for k, v := range opts { 122 | // C.av_dict_set(&p, C.CString(k), C.CString(v), 0) 123 | // } 124 | 125 | ret := C.avformat_write_header(context.ctype(), nil) 126 | if int(ret) < 0 { 127 | return fmt.Errorf("write header error, %s", C.av_err(ret)) 128 | } 129 | 130 | return nil 131 | } 132 | 133 | func (context *FormatContext) WriteTrailer() error { 134 | ret := C.av_write_trailer(context.ctype()) 135 | 136 | if int(ret) < 0 { 137 | return fmt.Errorf("write trailer error, %s", C.av_err(ret)) 138 | } 139 | 140 | return nil 141 | } 142 | 143 | func (context *FormatContext) WritePacket(packet *Packet) error { 144 | ret := C.av_interleaved_write_frame(context.ctype(), packet.ctype()) 145 | if int(ret) < 0 { 146 | return fmt.Errorf("write packet error, %s", C.av_err(ret)) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func (context *FormatContext) Streams() []*Stream { 153 | var s *C.struct_AVStream 154 | streamSize := unsafe.Sizeof(s) 155 | 156 | //TODO simplify it 157 | streams := make([]*Stream, context.ctype().nb_streams) 158 | for i := 0; i < len(streams); i++ { 159 | offset := uintptr(i) * streamSize 160 | streams[i] = (*Stream)(*(**C.struct_AVStream)(unsafe.Pointer(uintptr(unsafe.Pointer(context.ctype().streams)) + offset))) 161 | } 162 | 163 | return streams 164 | } 165 | 166 | func (context *FormatContext) ReadPacket(packet *Packet) error { 167 | ret := C.av_read_frame(context.ctype(), packet.ctype()) 168 | 169 | if int(ret) < 0 { 170 | return fmt.Errorf("read packet error, %s", C.av_err(ret)) 171 | } 172 | 173 | return nil 174 | } 175 | 176 | func (context *FormatContext) ctype() *C.struct_AVFormatContext { 177 | return (*C.struct_AVFormatContext)(unsafe.Pointer(context)) 178 | } 179 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alexdogonin/go-ffmpeg 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#cgo pkg-config: libavcodec 4 | //#cgo pkg-config: libswscale 5 | //#cgo pkg-config: libavformat 6 | //#cgo pkg-config: libavutil 7 | import "C" -------------------------------------------------------------------------------- /io_context.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | /* 4 | 5 | #include 6 | 7 | extern int Read(void *opaque, unsigned char *buff, int size); 8 | extern int Write(void *opaque, unsigned char *buff, int size); 9 | */ 10 | import "C" 11 | import ( 12 | "io" 13 | "sync" 14 | "sync/atomic" 15 | "unsafe" 16 | ) 17 | 18 | var contexts = sync.Map{} 19 | var contextsCounter int32 20 | 21 | type IOContext struct { 22 | id int 23 | r io.Reader 24 | w io.Writer 25 | c *C.struct_AVIOContext 26 | } 27 | 28 | func NewIOContext(source io.Reader, destination io.Writer) (*IOContext, error) { 29 | id := int(atomic.AddInt32(&contextsCounter, 1)) 30 | 31 | const ( 32 | bufferSize = 1024 33 | ) 34 | 35 | context := &IOContext{ 36 | id: id, 37 | r: source, 38 | w: destination, 39 | } 40 | 41 | const writable = 1 42 | c := C.avio_alloc_context( 43 | (*C.uchar)(C.av_malloc(bufferSize)), 44 | bufferSize, 45 | writable, 46 | unsafe.Pointer(&context.id), 47 | (*[0]byte)(C.Read), 48 | (*[0]byte)(C.Write), 49 | nil, 50 | ) 51 | 52 | context.c = c 53 | 54 | contexts.Store(context.id, context) 55 | 56 | return context, nil 57 | } 58 | 59 | func (context *IOContext) ctype() *C.struct_AVIOContext { 60 | return context.c 61 | } 62 | 63 | func (context *IOContext) Release() { 64 | C.avio_context_free(&context.c) 65 | 66 | contexts.Delete(context.id) 67 | } 68 | -------------------------------------------------------------------------------- /io_context_context_read_write.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | import "C" 4 | import ( 5 | "log" 6 | "unsafe" 7 | ) 8 | 9 | //export Read 10 | func Read(opaque unsafe.Pointer, buff unsafe.Pointer, size int) int { 11 | id := *(*int)(opaque) 12 | b := (*[bufferSize]byte)(buff) 13 | p, _ := contexts.Load(id) 14 | 15 | context := p.(*IOContext) 16 | n, err := context.r.Read((b)[:]) 17 | if err != nil { 18 | log.Println(err) 19 | return -1 20 | } 21 | 22 | return n 23 | } 24 | 25 | //export Write 26 | func Write(opaque unsafe.Pointer, buff unsafe.Pointer, size int) int { 27 | id := *(*int)(opaque) 28 | b := (*[bufferSize]byte)(buff) 29 | p, _ := contexts.Load(id) 30 | 31 | context := p.(*IOContext) 32 | n, err := context.w.Write((b)[:]) 33 | if err != nil { 34 | log.Println(err) 35 | return -1 36 | } 37 | 38 | return n 39 | } 40 | -------------------------------------------------------------------------------- /media_type.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | const ( 7 | MediaTypeUnknown = MediaType(C.AVMEDIA_TYPE_UNKNOWN) 8 | MediaTypeVideo = MediaType(C.AVMEDIA_TYPE_VIDEO) 9 | MediaTypeAudio = MediaType(C.AVMEDIA_TYPE_AUDIO) 10 | MediaTypeData = MediaType(C.AVMEDIA_TYPE_DATA) 11 | MediaTypeSubtitle = MediaType(C.AVMEDIA_TYPE_SUBTITLE) 12 | MediaTypeAttachment = MediaType(C.AVMEDIA_TYPE_ATTACHMENT) 13 | MediaTypeNB = MediaType(C.AVMEDIA_TYPE_NB) 14 | ) 15 | 16 | type MediaType int 17 | 18 | func (mt MediaType) ctype() C.enum_AVMediaType { 19 | return (C.enum_AVMediaType)(mt) 20 | } 21 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "unsafe" 9 | ) 10 | 11 | type Packet C.struct_AVPacket 12 | 13 | func NewPacket() (*Packet, error) { 14 | packet := (*Packet)(C.av_packet_alloc()) 15 | 16 | if packet == nil { 17 | return nil, errors.New("create packet error") 18 | } 19 | 20 | return packet, nil 21 | } 22 | 23 | func (packet *Packet) Release() { 24 | C.av_packet_free((**C.struct_AVPacket)(unsafe.Pointer(&packet))) 25 | } 26 | 27 | func (packet *Packet) Data() []byte { 28 | return C.GoBytes(unsafe.Pointer(packet.ctype().data), C.int(packet.ctype().size)) 29 | } 30 | 31 | func (packet *Packet) RescaleTimestamp(from, to Rational) { 32 | C.av_packet_rescale_ts(packet.ctype(), from.ctype(), to.ctype()) 33 | } 34 | 35 | func (packet *Packet) Unref() { 36 | C.av_packet_unref(packet.ctype()) 37 | } 38 | 39 | func (packet *Packet) SetStream(index int) { 40 | packet.ctype().stream_index = C.int(index) 41 | } 42 | 43 | func (packet *Packet) ctype() *C.struct_AVPacket { 44 | return (*C.struct_AVPacket)(unsafe.Pointer(packet)) 45 | } 46 | -------------------------------------------------------------------------------- /pixel_format.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | const ( 7 | RGBA PixelFormat = C.AV_PIX_FMT_RGBA 8 | YUV420P PixelFormat = C.AV_PIX_FMT_YUV420P 9 | YUVJ444P PixelFormat = C.AV_PIX_FMT_YUVJ444P 10 | YUV444P PixelFormat = C.AV_PIX_FMT_YUV444P 11 | ) 12 | 13 | type PixelFormat int //C.enum_AVPixelFormat 14 | 15 | func (fmt PixelFormat) ctype() C.enum_AVPixelFormat { 16 | return (C.enum_AVPixelFormat)(fmt) 17 | } 18 | -------------------------------------------------------------------------------- /rational.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | import "C" 4 | 5 | type Rational struct { 6 | Num, 7 | Den int 8 | } 9 | 10 | func (rational Rational) ctype() C.struct_AVRational { 11 | return C.struct_AVRational{C.int(rational.Num), C.int(rational.Den)} 12 | } 13 | -------------------------------------------------------------------------------- /sample_format.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type SampleFormat int 7 | 8 | const ( 9 | SampleFormatNone SampleFormat = iota - 1 10 | SampleFormatU8 ///< unsigned 8 bits 11 | SampleFormatS16 ///< signed 16 bits 12 | SampleFormatS32 ///< signed 32 bits 13 | SampleFormatFLT ///< float 14 | SampleFormatDBL ///< double 15 | SampleFormatU8P ///< unsigned 8 bits, planar 16 | SampleFormatS16P ///< signed 16 bits, planar 17 | SampleFormatS32P ///< signed 32 bits, planar 18 | SampleFormatFLTP ///< float, planar 19 | SampleFormatDBLP ///< double, planar 20 | SampleFormatS64 ///< signed 64 bits 21 | SampleFormatS64P ///< signed 64 bits, planar 22 | SampleFormatNB ///< Number of sample formats. DO NOT USE if linking dynamically 23 | ) 24 | 25 | func (sformat SampleFormat) ctype() C.enum_AVSampleFormat { 26 | return (C.enum_AVSampleFormat)(sformat) 27 | } 28 | -------------------------------------------------------------------------------- /scale.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "unsafe" 9 | ) 10 | 11 | type ScaleContext C.struct_SwsContext 12 | 13 | func NewScaleContext(srcWidth, srcHeight int, srcPixFmt PixelFormat, dstWidth, dstHeight int, dstPixFmt PixelFormat) (*ScaleContext, error) { 14 | convertContext := (*ScaleContext)(C.sws_getContext( 15 | C.int(srcWidth), 16 | C.int(srcHeight), 17 | RGBA.ctype(), 18 | C.int(dstWidth), 19 | C.int(dstHeight), 20 | YUV420P.ctype(), 21 | C.SWS_GAUSS, 22 | (*C.struct_SwsFilter)(nil), 23 | (*C.struct_SwsFilter)(nil), 24 | (*C.double)(nil), 25 | )) 26 | 27 | if convertContext == nil { 28 | return nil, errors.New("create convetr context error") 29 | } 30 | 31 | return convertContext, nil 32 | } 33 | 34 | func (scaleContext *ScaleContext) Release() { 35 | C.sws_freeContext(scaleContext.ctype()) 36 | } 37 | 38 | func (scaleContext *ScaleContext) Scale(src, dst *VideoFrame) { 39 | C.sws_scale( 40 | scaleContext.ctype(), 41 | &(src.ctype().data[0]), 42 | &(src.ctype().linesize[0]), 43 | C.int(0), 44 | src.ctype().height, 45 | &(dst.ctype().data[0]), 46 | &(dst.ctype().linesize[0]), 47 | ) 48 | } 49 | 50 | func (scaleContext *ScaleContext) ctype() *C.struct_SwsContext { 51 | return (*C.struct_SwsContext)(unsafe.Pointer(scaleContext)) 52 | } 53 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "unsafe" 9 | ) 10 | 11 | type Stream C.struct_AVStream 12 | 13 | // NewStream creates new stream and add them to formatContext.streams 14 | func NewStream(formatContext *FormatContext) (*Stream, error) { 15 | stream := (*Stream)(unsafe.Pointer(C.avformat_new_stream(formatContext.ctype(), nil))) 16 | 17 | if stream == nil { 18 | return nil, errors.New("create stream error") 19 | } 20 | 21 | stream.id = C.int(formatContext.nb_streams - 1) 22 | 23 | return stream, nil 24 | } 25 | 26 | func (s *Stream) SetCodecParameters(parms *CodecParameters) { 27 | *(s.ctype().codecpar) = *(parms.ctype()) 28 | } 29 | 30 | func (s *Stream) CodecParameters() *CodecParameters { 31 | p := CodecParameters{ 32 | ExtraData: C.GoBytes(unsafe.Pointer(s.ctype().codecpar.extradata), s.ctype().codecpar.extradata_size), 33 | CodecType: MediaType(s.ctype().codecpar.codec_type), 34 | CodecID: CodecID(s.ctype().codecpar.codec_id), 35 | CodecTag: uint32(s.ctype().codecpar.codec_tag), 36 | Format: int(s.ctype().codecpar.format), 37 | Bitrate: int(s.ctype().codecpar.bit_rate), 38 | BitsPerCodedSample: int(s.ctype().codecpar.bits_per_coded_sample), 39 | BitsPerRawSample: int(s.ctype().codecpar.bits_per_raw_sample), 40 | Profile: int(s.ctype().codecpar.profile), 41 | Level: int(s.ctype().codecpar.level), 42 | Width: int(s.ctype().codecpar.width), 43 | Height: int(s.ctype().codecpar.height), 44 | SampleAspectRatio: Rational{int(s.ctype().codecpar.sample_aspect_ratio.num), int(s.ctype().codecpar.sample_aspect_ratio.den)}, 45 | FieldOrder: FieldOrder(s.ctype().codecpar.field_order), 46 | ColorRange: ColorRange(s.ctype().codecpar.color_range), 47 | ColorPrimaries: ColorPrimaries(s.ctype().codecpar.color_primaries), 48 | ColorTrc: ColorTransferCharacteristic(s.ctype().codecpar.color_trc), 49 | ColorSpace: ColorSpace(s.ctype().codecpar.color_space), 50 | ChromaLocation: ChromaLocation(s.ctype().codecpar.chroma_location), 51 | VideoDelay: int(s.ctype().codecpar.video_delay), 52 | ChannelLayout: ChannelLayout(s.ctype().codecpar.channel_layout), 53 | Channels: int(s.ctype().codecpar.channels), 54 | SampleRate: int(s.ctype().codecpar.sample_rate), 55 | BlockAlign: int(s.ctype().codecpar.block_align), 56 | FrameSize: int(s.ctype().codecpar.frame_size), 57 | InitialPadding: int(s.ctype().codecpar.initial_padding), 58 | TrailingPadding: int(s.ctype().codecpar.trailing_padding), 59 | SeekPreroll: int(s.ctype().codecpar.seek_preroll), 60 | } 61 | 62 | return &p 63 | } 64 | 65 | func (s *Stream) TimeBase() Rational { 66 | return Rational{int(s.ctype().time_base.num), int(s.ctype().time_base.den)} 67 | } 68 | 69 | func (s *Stream) ctype() *C.struct_AVStream { 70 | return (*C.struct_AVStream)(unsafe.Pointer(s)) 71 | } 72 | -------------------------------------------------------------------------------- /video_codec_context.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "unsafe" 10 | ) 11 | 12 | var ( 13 | EAGAIN = errors.New("EAGAIN") 14 | EOF = errors.New("EOF") 15 | ) 16 | 17 | type VideoCodecContext struct { 18 | context *C.struct_AVCodecContext 19 | 20 | err error 21 | } 22 | 23 | /*NewCodecContext creates new codec context and try to open codec 24 | 25 | Recomended bitrates (https://support.google.com/youtube/answer/1722171?hl=en): 26 | Type | Video Bitrate, | Video Bitrate, 27 | | Standard Frame Rate | High Frame Rate 28 | | (24, 25, 30) | (48, 50, 60) 29 | ----------------------------------------------------- 30 | 2160p (4k) | 35-45 Mbps | 53-68 Mbps 31 | 1440p (2k) | 16 Mbps | 24 Mbps 32 | 1080p | 8 Mbps | 12 Mbps 33 | 720p | 5 Mbps | 7.5 Mbps 34 | 480p | 2.5 Mbps | 4 Mbps 35 | 360p | 1 Mbps | 1.5 Mbps 36 | */ 37 | func NewVideoCodecContext(codec *Codec, width, height int, pixFmt PixelFormat, opts ...VideoCodecContextOpt) (*VideoCodecContext, error) { 38 | context := C.avcodec_alloc_context3((*C.struct_AVCodec)(codec)) 39 | 40 | const defaultFramerate = 25 41 | 42 | context.width = C.int(width) 43 | context.height = C.int(height) 44 | context.time_base = C.AVRational{C.int(1), C.int(defaultFramerate)} 45 | context.framerate = C.AVRational{C.int(defaultFramerate), C.int(1)} 46 | context.gop_size = C.int(10) 47 | context.pix_fmt = pixFmt.ctype() 48 | 49 | context.bit_rate = C.long(calculateBitrate(int(context.height), int(context.framerate.num))) 50 | 51 | codecCtx := &VideoCodecContext{ 52 | context: context, 53 | } 54 | 55 | for _, opt := range opts { 56 | opt(codecCtx) 57 | } 58 | 59 | if int(context.framerate.num) != defaultFramerate { 60 | context.bit_rate = C.long(calculateBitrate(int(context.height), int(context.framerate.num))) 61 | } 62 | 63 | if ok := int(C.avcodec_open2(context, (*C.struct_AVCodec)(codec), nil)) == 0; !ok { 64 | return nil, errors.New("codec open error") 65 | } 66 | 67 | return codecCtx, nil 68 | } 69 | 70 | func (context *VideoCodecContext) Release() { 71 | C.avcodec_free_context(&context.context) 72 | } 73 | 74 | func (context *VideoCodecContext) SendFrame(frame *VideoFrame) error { 75 | if context.err != nil { 76 | return context.err 77 | } 78 | 79 | if int(C.avcodec_send_frame(context.context, frame.ctype())) != 0 { 80 | return errors.New("send frame error") 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (context *VideoCodecContext) ReceivePacket(dest *Packet) bool { 87 | if context.err != nil { 88 | return false 89 | } 90 | 91 | ret := int(C.avcodec_receive_packet(context.context, dest.ctype())) 92 | if ret == -int(C.EAGAIN) || ret == int(C.AVERROR_EOF) { 93 | return false 94 | } 95 | 96 | if ret < 0 { 97 | context.err = fmt.Errorf("error during encoding (code = %q)", ret) 98 | return false 99 | } 100 | 101 | return true 102 | } 103 | 104 | func (context *VideoCodecContext) Err() error { 105 | return context.err 106 | } 107 | 108 | func (context *VideoCodecContext) CodecParameters() *CodecParameters { 109 | parms := &CodecParameters{ 110 | ColorPrimaries: ColorPrimaries_Unspecified, 111 | ColorTrc: ColorTransferCharacteristic_Unspecified, 112 | ColorSpace: ColorSpace_Unspecified, 113 | SampleAspectRatio: Rational{0, 1}, 114 | CodecType: MediaType(context.context.codec_type), 115 | CodecID: CodecID(context.context.codec_id), 116 | CodecTag: uint32(context.context.codec_tag), 117 | Bitrate: int(context.context.bit_rate), 118 | BitsPerCodedSample: int(context.context.bits_per_coded_sample), 119 | BitsPerRawSample: int(context.context.bits_per_raw_sample), 120 | Profile: int(context.context.profile), 121 | Level: int(context.context.level), 122 | Format: -1, 123 | } 124 | 125 | switch MediaType(parms.CodecType) { 126 | case MediaTypeVideo: 127 | parms.Format = int(context.context.pix_fmt) 128 | parms.Width = int(context.context.width) 129 | parms.Height = int(context.context.height) 130 | parms.FieldOrder = FieldOrder(context.context.field_order) 131 | parms.ColorRange = ColorRange(context.context.color_range) 132 | parms.ColorPrimaries = ColorPrimaries(context.context.color_primaries) 133 | parms.ColorTrc = ColorTransferCharacteristic(context.context.color_trc) 134 | parms.ColorSpace = ColorSpace(context.context.colorspace) 135 | parms.ChromaLocation = ChromaLocation(context.context.chroma_sample_location) 136 | parms.SampleAspectRatio = Rational{int(context.context.sample_aspect_ratio.num), int(context.context.sample_aspect_ratio.den)} 137 | parms.VideoDelay = int(context.context.has_b_frames) 138 | case MediaTypeAudio: 139 | parms.Format = int(context.context.sample_fmt) 140 | parms.ChannelLayout = ChannelLayout(context.context.channel_layout) 141 | parms.Channels = int(context.context.channels) 142 | parms.SampleRate = int(context.context.sample_rate) 143 | parms.BlockAlign = int(context.context.block_align) 144 | parms.FrameSize = int(context.context.frame_size) 145 | parms.InitialPadding = int(context.context.initial_padding) 146 | parms.TrailingPadding = int(context.context.trailing_padding) 147 | parms.SeekPreroll = int(context.context.seek_preroll) 148 | case MediaTypeSubtitle: 149 | parms.Width = int(context.context.width) 150 | parms.Height = int(context.context.height) 151 | } 152 | 153 | if context.context.extradata != nil { 154 | parms.ExtraData = C.GoBytes(unsafe.Pointer(context.context.extradata), context.context.extradata_size) 155 | } 156 | 157 | return parms 158 | } 159 | 160 | func calculateBitrate(height, framerate int) int { 161 | switch framerate { 162 | case 24, 25, 30: 163 | switch height { 164 | case 2160: 165 | return 40 * 1024 * 1024 166 | case 1440: 167 | return 16 * 1024 * 1024 168 | case 1080: 169 | return 8 * 1024 * 1024 170 | case 720: 171 | return 5 * 1024 * 1024 172 | case 480: 173 | return 2.5 * 1024 * 1024 174 | case 360: 175 | return 1 * 1024 * 1024 176 | } 177 | case 48, 50, 60: 178 | switch height { 179 | case 2160: 180 | return 60 * 1024 * 1024 181 | case 1440: 182 | return 24 * 1024 * 1024 183 | case 1080: 184 | return 12 * 1024 * 1024 185 | case 720: 186 | return 7.5 * 1024 * 1024 187 | case 480: 188 | return 4 * 1024 * 1024 189 | case 360: 190 | return 1.5 * 1024 * 1024 191 | } 192 | } 193 | 194 | width := height * 16 / 9 195 | coef := 6 196 | 197 | return width * height * framerate / coef 198 | } 199 | -------------------------------------------------------------------------------- /video_codec_context_opt.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | import "C" 5 | 6 | type VideoCodecContextOpt func(*VideoCodecContext) 7 | 8 | func WithVideoBitrate(bitrate int) VideoCodecContextOpt { 9 | return func(codecCtx *VideoCodecContext) { 10 | codecCtx.context.bit_rate = C.long(bitrate) 11 | } 12 | } 13 | 14 | func WithResolution(width, height int) VideoCodecContextOpt { 15 | return func(codecCtx *VideoCodecContext) { 16 | codecCtx.context.width = C.int(width) 17 | codecCtx.context.height = C.int(height) 18 | } 19 | } 20 | 21 | func WithFramerate(framerate int) VideoCodecContextOpt { 22 | return func(codecCtx *VideoCodecContext) { 23 | codecCtx.context.time_base = C.AVRational{C.int(1), C.int(framerate)} 24 | codecCtx.context.framerate = C.AVRational{C.int(framerate), C.int(1)} 25 | } 26 | } 27 | 28 | func WithPixFmt(pixFmt PixelFormat) VideoCodecContextOpt { 29 | return func(codecCtx *VideoCodecContext) { 30 | codecCtx.context.pix_fmt = pixFmt.ctype() 31 | } 32 | } 33 | 34 | func WithGopSize(gopSize int) VideoCodecContextOpt { 35 | return func(codecCtx *VideoCodecContext) { 36 | codecCtx.context.gop_size = C.int(gopSize) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /video_frame.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | //#include 4 | //#include 5 | import "C" 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "image" 11 | "unsafe" 12 | ) 13 | 14 | var pixelFormatSubsampleRatio = map[PixelFormat]image.YCbCrSubsampleRatio{ 15 | YUV420P: image.YCbCrSubsampleRatio420, 16 | YUVJ444P: image.YCbCrSubsampleRatio444, 17 | YUV444P: image.YCbCrSubsampleRatio444, 18 | } 19 | 20 | type VideoFrame C.struct_AVFrame 21 | 22 | func NewVideoFrame(width, height int, pixFmt PixelFormat) (*VideoFrame, error) { 23 | frame := (*VideoFrame)(unsafe.Pointer(C.av_frame_alloc())) 24 | 25 | frame.format = C.int(pixFmt) 26 | frame.width = C.int(width) 27 | frame.height = C.int(height) 28 | if ret := C.av_frame_get_buffer(frame.ctype(), C.int(1) /*alignment*/); ret < 0 { 29 | frame.Release() 30 | return nil, fmt.Errorf("Error allocating avframe buffer. Err: %v", ret) 31 | } 32 | 33 | return frame, nil 34 | } 35 | 36 | func (frame *VideoFrame) SetPts(pts int) { 37 | frame.ctype().pts = C.long(pts) 38 | } 39 | 40 | func (frame *VideoFrame) Release() { 41 | C.av_frame_free((**C.struct_AVFrame)(unsafe.Pointer(&frame))) 42 | } 43 | 44 | func (frame *VideoFrame) MakeWritable() error { 45 | if 0 != int(C.av_frame_make_writable(frame.ctype())) { 46 | return errors.New("make writable error") 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (frame *VideoFrame) FillRGBA(img *image.RGBA) error { 53 | if PixelFormat(frame.format) != RGBA { 54 | return errors.New("pixel format of frame is not RGBA") 55 | } 56 | 57 | if !img.Bounds().Eq(image.Rect(0, 0, int(frame.ctype().width), int(frame.ctype().height))) { 58 | return errors.New("image resolution not equal frame resolution") 59 | } 60 | 61 | ok := 0 <= int(C.av_image_fill_arrays( 62 | &(frame.data[0]), 63 | &(frame.linesize[0]), 64 | (*C.uint8_t)(&img.Pix[0]), 65 | int32(frame.format), 66 | frame.width, 67 | frame.height, 68 | 1, 69 | )) 70 | 71 | if !ok { 72 | return errors.New("Could not fill raw picture buffer") 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (frame *VideoFrame) FillYCbCr(img *image.YCbCr) error { 79 | ratio, ok := pixelFormatSubsampleRatio[PixelFormat(frame.format)] 80 | if !ok { 81 | return errors.New("pixel format of frame is not YCbCr") 82 | } 83 | 84 | if ratio != img.SubsampleRatio { 85 | return errors.New("subsample ration of frame not equal image") 86 | } 87 | 88 | if !img.Bounds().Eq(image.Rect(0, 0, int(frame.ctype().width), int(frame.ctype().height))) { 89 | return errors.New("image resolution not equal frame resolution") 90 | } 91 | 92 | data := make([]uint8, len(img.Y)+len(img.Cb)+len(img.Cr)) 93 | copy(data[:len(img.Y)], img.Y) 94 | copy(data[len(img.Y):len(img.Y)+len(img.Cb)], img.Cb) 95 | copy(data[len(img.Y)+len(img.Cb):], img.Cr) 96 | 97 | ok = 0 <= int(C.av_image_fill_arrays( 98 | &(frame.data[0]), 99 | &(frame.linesize[0]), 100 | (*C.uint8_t)(&(data[0])), 101 | int32(frame.format), 102 | frame.width, 103 | frame.height, 104 | 1, 105 | )) 106 | 107 | if !ok { 108 | return errors.New("Could not fill raw picture buffer") 109 | } 110 | 111 | return nil 112 | } 113 | 114 | func (frame *VideoFrame) ctype() *C.struct_AVFrame { 115 | return (*C.struct_AVFrame)(unsafe.Pointer(frame)) 116 | } 117 | --------------------------------------------------------------------------------