Pure Go FFmpeg bindings without CGO. Decode, encode, transcode, and process media files with zero C dependencies at build time.
- Pure Go builds - No CGO required (
CGO_ENABLED=0 go buildjust works) - Cross-compilation - Build for any platform from any platform
- Full media support - Decode, encode, transcode, mux, demux, scale
- Custom I/O - Integrate with
io.Reader/io.Writeror custom callbacks - Hardware acceleration - CUDA, VA-API, VideoToolbox support
- FFmpeg 4.x-7.x - Works with all modern FFmpeg versions
- Idiomatic Go API - Clean, simple interfaces that feel native
# Install FFmpeg libraries
sudo apt install ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev # Ubuntu/Debian
brew install ffmpeg # macOS
# Install ffgo
go get github.com/obinnaokechukwu/ffgopackage main
import (
"fmt"
"github.com/obinnaokechukwu/ffgo"
)
func main() {
decoder, err := ffgo.NewDecoder("input.mp4")
if err != nil {
panic(err)
}
defer decoder.Close()
fmt.Printf("Video: %dx%d @ %v fps\n",
decoder.VideoStream().Width,
decoder.VideoStream().Height,
decoder.VideoStream().FrameRate)
for {
frame, err := decoder.ReadFrame()
if err != nil {
panic(err)
}
if frame == nil {
break // EOF
}
// Process frame...
}
}func transcode(input, output string) error {
decoder, err := ffgo.NewDecoder(input)
if err != nil {
return err
}
defer decoder.Close()
encoder, err := ffgo.NewEncoder(output, &ffgo.EncoderOptions{
Video: &ffgo.VideoEncoderConfig{
Codec: ffgo.CodecH264,
Width: decoder.VideoStream().Width,
Height: decoder.VideoStream().Height,
FrameRate: decoder.VideoStream().FrameRate,
Bitrate: 2_000_000,
},
})
if err != nil {
return err
}
defer encoder.Close()
for {
frame, err := decoder.ReadFrame()
if err != nil {
return err
}
if frame == nil {
break
}
if frame.MediaType() == ffgo.MediaTypeVideo {
// ReadFrame returns a *FrameWrapper; pass the underlying ffgo.Frame to the encoder.
if err := encoder.WriteVideoFrame(frame.Raw()); err != nil {
return err
}
}
}
return nil
}CGO_ENABLED=0 go build -o myapp# Build for Windows from Linux
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o myapp.exe
# Build for Linux ARM64 from macOS
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o myapp| OS | Architecture | Status |
|---|---|---|
| Linux | amd64, arm64 | ✅ Fully supported |
| macOS | amd64, arm64 | ✅ Fully supported |
| Windows | amd64 | ✅ Fully supported |
| Windows | arm64 | |
| FreeBSD | amd64, arm64 |
Not supported: iOS, Android, 32-bit systems
- User Guide - Complete API documentation with examples
- Internal Design - Architecture and implementation details
- Gap Analysis - FFmpeg vs ffgo feature comparison
- Examples - Working code examples
ffgo uses purego to call FFmpeg's C libraries without CGO:
┌─────────────────────────┐
│ Your Application │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ ffgo Public API │
│ (Decoder, Encoder...) │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ purego + shim │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ FFmpeg Libraries │
│ (libavcodec, etc.) │
└─────────────────────────┘
A tiny C shim library handles FFmpeg features that pure Go cannot call safely/portably via dynamic bindings:
- Variadic APIs (e.g.
av_log) for logging callbacks. - Selected field access for newer FFmpeg builds where struct layouts vary (notably macOS runners with FFmpeg 7.x). This keeps hardware-accel setup and some container metadata (duration/programs/chapters) reliable without hardcoding brittle offsets.
The shim is still optional in the sense that ffgo will run without it, but on some macOS/FFmpeg combinations, features like hardware decode or accurate duration/chapters/programs may require the shim to be present.
This module includes prebuilt shims under shim/prebuilt/<os>-<arch>/ and will load them automatically when present (no user compilation required). If your platform/arch isn’t shipped yet, you can build one with cd shim && ./build.sh.
file, _ := os.Open("video.mp4")
decoder, err := ffgo.NewDecoderFromReader(file, "mp4")For live or channel-backed input, the read callback is used while the decoder
constructor probes the stream, so feed valid media bytes concurrently and do not
return 0, nil for "no data yet". RTP payloads usually need codec-specific
depacketization before they can be passed to ffgo as a raw elementary stream
such as "h264" or "hevc".
scaler, err := ffgo.NewScaler(
1920, 1080, ffgo.PixelFormatYUV420P,
1280, 720, ffgo.PixelFormatYUV420P,
ffgo.ScaleBilinear,
)
scaledFrame, err := scaler.Scale(videoFrame)decoder, err := ffgo.NewDecoder("input.mp4",
ffgo.WithHWDevice("cuda"), // NVIDIA GPU
)resampler, err := ffgo.NewResampler(
ffgo.AudioFormat{SampleRate: 44100, Channels: 2, SampleFormat: ffgo.SampleFormatS16},
ffgo.AudioFormat{SampleRate: 48000, Channels: 2, SampleFormat: ffgo.SampleFormatFLTP},
)
outputFrame, err := resampler.Resample(inputFrame)filterGraph, err := ffgo.NewVideoFilterGraph(&ffgo.FilterGraphConfig{
Width: 1920,
Height: 1080,
PixelFmt: ffgo.PixelFormatYUV420P,
Filters: "scale=1280:720,hflip,vflip",
})
filteredFrame, err := filterGraph.ProcessFrame(inputFrame)decoder, err := ffgo.NewConcatDecoder(
[]string{"video1.mp4", "video2.mp4", "video3.mp4"},
ffgo.WithConcatSafeMode(false),
)err := ffgo.TwoPassTranscode("input.mp4", "output.mp4", &ffgo.EncoderOptions{
Video: &ffgo.VideoEncoderConfig{
Codec: ffgo.CodecH264,
Bitrate: 2_000_000,
Preset: "medium",
},
})segmenter, err := ffgo.NewHLSSegmenter(&ffgo.HLSOptions{
SegmentTime: 6,
ListSize: 5,
OutputDir: "./hls",
SegmentFilename: "segment_%03d.ts",
PlaylistFilename: "playlist.m3u8",
})ffgo.SetLogLevel(ffgo.LogWarning)
ffgo.SetLogCallback(func(level ffgo.LogLevel, msg string) {
log.Printf("[FFmpeg] %s", msg)
})ffgo adds minimal overhead (~50-100ns per function call) compared to CGO, which is negligible for media processing workloads. Expect:
- H.264 1080p decode: 200+ fps (with HW), 60+ fps (software)
- H.264 1080p encode: 30+ fps (software, medium preset)
- Frame scaling 1080p→720p: 500+ fps
| Package | Description |
|---|---|
ffgo |
High-level API (recommended) |
ffgo/avutil |
Low-level avutil bindings |
ffgo/avcodec |
Low-level avcodec bindings |
ffgo/avformat |
Low-level avformat bindings |
ffgo/swscale |
Low-level swscale bindings |
# Run tests
go test ./...
# Run examples
go run examples/decode/main.go testdata/test.mp4
go run examples/transcode/main.go input.mp4 output.mp4ffgo provides ~95% coverage of FFmpeg's functionality needed for professional media workflows:
- Decode/Encode/Transcode - All major video/audio codecs (H.264, HEVC, VP8/9, AV1, AAC, MP3, Opus, etc.)
- Container Support - MP4, MKV, AVI, MOV, WebM, FLV, MPEG-TS, and more
- Custom I/O - Stream from/to
io.Reader/io.Writeror custom callbacks - Hardware Acceleration - CUDA, VA-API, VideoToolbox, DXVA2, QSV
- Pixel/Sample Conversion - All pixel formats and sample rates via swscale/swresample
- Filter Graphs - Complex video/audio filter chains (crop, scale, overlay, volume, etc.)
- Subtitle Support - Text (SRT, ASS, WebVTT) and bitmap subtitle extraction/rendering
- Metadata Handling - Read/write container and stream-level metadata
- Advanced Seeking - Frame-accurate seeking with thumbnail extraction
- Stream Copy - Fast remuxing without re-encoding
- Bitstream Filters - Packet-level transformations (h264_mp4toannexb, etc.)
- Multi-Pass Encoding - Two-pass VBR for optimal quality/size
- HLS/DASH Segmentation - Live streaming segment generation
- Network Streaming - RTMP, HTTP, UDP output with reconnect support
- Concat Demuxer - Seamless concatenation of multiple files
- Format Probing - Detailed format detection with confidence scores
- Multi-Program Streams - MPEG-TS program selection
- Data Streams - Arbitrary data track support
- Color Space Control - Explicit BT.601/709/2020 matrix selection
- Frame Timing - PTS/DTS utilities and timestamp validation
- Frame Pooling - Memory-efficient frame reuse
- Device Capture - Screen/camera capture (requires libavdevice + OS permissions)
See Gap Analysis for detailed feature comparison with FFmpeg.
Q: Why not just use CGO? A: CGO prevents cross-compilation, slows builds, increases binary size, and complicates CI/CD.
Q: What's the performance impact? A: Negligible. Media processing is CPU-bound; function call overhead is <1% of total time.
Q: Can I statically link FFmpeg?
A: No. ffgo uses dynamic linking via dlopen. Static linking requires CGO.
Q: Does this work on mobile? A: No. iOS and Android require CGO for purego. Use a CGO-based binding for mobile.
Contributions welcome! Please read CONTRIBUTING.md first.
Apache License 2.0 - see LICENSE file.
FFmpeg is licensed under LGPL/GPL. Ensure your usage complies with FFmpeg's license.
- Built with purego by Hajime Hoshi
- Inspired by the FFmpeg project and community
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: docs/user-guide.md