├── README.md ├── ffmpeg_test.go ├── LICENSE └── ffmpeg.go /README.md: -------------------------------------------------------------------------------- 1 | Very basic ffmpeg video encoder bindings. 2 | 3 | Don't use. Unless you know what you're doing. -------------------------------------------------------------------------------- /ffmpeg_test.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "log" 8 | "os" 9 | "time" 10 | ) 11 | 12 | func ExampleEncoderUsage() { 13 | 14 | f, err := os.Create("test.mpeg") 15 | if err != nil { 16 | log.Panicf("Unable to open output file: %q", err) 17 | } 18 | 19 | im := image.NewRGBA(image.Rect(0, 0, 640, 480)) 20 | 21 | e, err := NewEncoder(CODEC_ID_H264, im, f) 22 | if err != nil { 23 | log.Panicf("Unable to start encoder: %q", err) 24 | } 25 | 26 | start := time.Now() 27 | 28 | for i := 0; i < 25*5; i++ { 29 | c := color.RGBA{0, 0, uint8(i % 255), 255} 30 | // uint8(i%255), uint8(i%255), 255} 31 | draw.Draw(im, im.Bounds(), &image.Uniform{c}, image.ZP, draw.Src) 32 | 33 | err := e.WriteFrame() 34 | if err != nil { 35 | log.Panicf("Problem writing frame: %q", err) 36 | } 37 | } 38 | 39 | e.Close() 40 | 41 | log.Printf("Took %s", time.Since(start)) 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Peter Waller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ffmpeg.go: -------------------------------------------------------------------------------- 1 | package ffmpeg 2 | 3 | // #include 4 | // #include 5 | // 6 | // // ... yes. Don't ask. 7 | // typedef struct SwsContext SwsContext; 8 | // 9 | // #ifndef PIX_FMT_RGB0 10 | // #define PIX_FMT_RGB0 PIX_FMT_RGB32 11 | // #endif 12 | // 13 | // #cgo pkg-config: libavdevice libavformat libavfilter libavcodec libswscale libavutil 14 | import "C" 15 | 16 | import ( 17 | "fmt" 18 | "image" 19 | "io" 20 | "log" 21 | "reflect" 22 | "unsafe" 23 | ) 24 | 25 | const ( 26 | CODEC_ID_H264 = C.CODEC_ID_H264 27 | ) 28 | 29 | type Encoder struct { 30 | codec uint32 31 | im image.Image 32 | underlying_im image.Image 33 | Output io.Writer 34 | 35 | _codec *C.AVCodec 36 | _context *C.AVCodecContext 37 | _swscontext *C.SwsContext 38 | _frame *C.AVFrame 39 | _outbuf []byte 40 | } 41 | 42 | func init() { 43 | C.avcodec_register_all() 44 | } 45 | 46 | func ptr(buf []byte) *C.uint8_t { 47 | h := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) 48 | return (*C.uint8_t)(unsafe.Pointer(h.Data)) 49 | } 50 | 51 | /* 52 | type EncoderOptions struct { 53 | BitRate uint32 54 | W, H int 55 | TimeBase 56 | } */ 57 | 58 | /* 59 | var DefaultEncoderOptions = EncoderOptions{ 60 | BitRate:400000, 61 | W: 0, H: 0, 62 | c.time_base = C.AVRational{1,25} 63 | c.gop_size = 10 64 | c.max_b_frames = 1 65 | c.pix_fmt = C.PIX_FMT_RGB 66 | } */ 67 | 68 | func NewEncoder(codec uint32, in image.Image, out io.Writer) (*Encoder, error) { 69 | _codec := C.avcodec_find_encoder(codec) 70 | if _codec == nil { 71 | return nil, fmt.Errorf("could not find codec") 72 | } 73 | 74 | c := C.avcodec_alloc_context3(_codec) 75 | f := C.avcodec_alloc_frame() 76 | 77 | c.bit_rate = 400000 78 | 79 | // resolution must be a multiple of two 80 | w, h := C.int(in.Bounds().Dx()), C.int(in.Bounds().Dy()) 81 | if w%2 == 1 || h%2 == 1 { 82 | return nil, fmt.Errorf("Bad image dimensions (%d, %d), must be even", w, h) 83 | } 84 | 85 | log.Printf("Encoder dimensions: %d, %d", w, h) 86 | 87 | c.width = w 88 | c.height = h 89 | c.time_base = C.AVRational{1, 25} // FPS 90 | c.gop_size = 10 // emit one intra frame every ten frames 91 | c.max_b_frames = 1 92 | 93 | underlying_im := image.NewYCbCr(in.Bounds(), image.YCbCrSubsampleRatio420) 94 | c.pix_fmt = C.PIX_FMT_YUV420P 95 | f.data[0] = ptr(underlying_im.Y) 96 | f.data[1] = ptr(underlying_im.Cb) 97 | f.data[2] = ptr(underlying_im.Cr) 98 | f.linesize[0] = w 99 | f.linesize[1] = w / 2 100 | f.linesize[2] = w / 2 101 | 102 | if C.avcodec_open2(c, _codec, nil) < 0 { 103 | return nil, fmt.Errorf("could not open codec") 104 | } 105 | 106 | _swscontext := C.sws_getContext(w, h, C.PIX_FMT_RGB0, w, h, C.PIX_FMT_YUV420P, 107 | C.SWS_BICUBIC, nil, nil, nil) 108 | 109 | e := &Encoder{codec, in, underlying_im, out, _codec, c, _swscontext, f, make([]byte, 16*1024)} 110 | return e, nil 111 | } 112 | 113 | func (e *Encoder) WriteFrame() error { 114 | e._frame.pts = C.int64_t(e._context.frame_number) 115 | 116 | var input_data [3]*C.uint8_t 117 | var input_linesize [3]C.int 118 | 119 | switch im := e.im.(type) { 120 | case *image.RGBA: 121 | bpp := 4 122 | input_data = [3]*C.uint8_t{ptr(im.Pix)} 123 | input_linesize = [3]C.int{C.int(e.im.Bounds().Dx() * bpp)} 124 | case *image.NRGBA: 125 | bpp := 4 126 | input_data = [3]*C.uint8_t{ptr(im.Pix)} 127 | input_linesize = [3]C.int{C.int(e.im.Bounds().Dx() * bpp)} 128 | default: 129 | panic("Unknown input image type") 130 | } 131 | 132 | // Perform scaling from input type to output type 133 | C.sws_scale(e._swscontext, &input_data[0], &input_linesize[0], 134 | 0, e._context.height, 135 | &e._frame.data[0], &e._frame.linesize[0]) 136 | 137 | outsize := C.avcodec_encode_video(e._context, ptr(e._outbuf), 138 | C.int(len(e._outbuf)), e._frame) 139 | 140 | if outsize == 0 { 141 | return nil 142 | } 143 | 144 | n, err := e.Output.Write(e._outbuf[:outsize]) 145 | if err != nil { 146 | return err 147 | } 148 | if n < int(outsize) { 149 | return fmt.Errorf("Short write, expected %d, wrote %d", outsize, n) 150 | } 151 | 152 | return nil 153 | } 154 | 155 | func (e *Encoder) Close() { 156 | 157 | // Process "delayed" frames 158 | for { 159 | outsize := C.avcodec_encode_video(e._context, ptr(e._outbuf), 160 | C.int(len(e._outbuf)), nil) 161 | 162 | if outsize == 0 { 163 | break 164 | } 165 | 166 | n, err := e.Output.Write(e._outbuf[:outsize]) 167 | if err != nil { 168 | panic(err) 169 | } 170 | if n < int(outsize) { 171 | panic(fmt.Errorf("Short write, expected %d, wrote %d", outsize, n)) 172 | } 173 | } 174 | 175 | n, err := e.Output.Write([]byte{0, 0, 1, 0xb7}) 176 | if err != nil || n != 4 { 177 | log.Panicf("Error finishing mpeg file: %q; n = %d", err, n) 178 | } 179 | 180 | C.avcodec_close((*C.AVCodecContext)(unsafe.Pointer(e._context))) 181 | C.av_free(unsafe.Pointer(e._context)) 182 | C.av_free(unsafe.Pointer(e._frame)) 183 | e._frame, e._codec = nil, nil 184 | } 185 | --------------------------------------------------------------------------------