go-ffmpeg-ffi loads FFmpeg at runtime. Installing the Go module is only half of the setup: the application must also provide a coherent supported FFmpeg shared- library family for its operating system and architecture.
- Go 1.22.2 or newer.
- FFmpeg 6.x, 7.x, 8.x, or 9.x shared libraries from one coherent build.
- A 64-bit target covered by the support matrix.
- Android NDK or Xcode and
CGO_ENABLED=1for mobile builds.
The FFmpeg command-line program is not used by the Go API. It is useful for
diagnostics, but the required runtime components are the shared libraries such
as avutil, avcodec, avformat, swscale, and swresample. Optional
features load avfilter and avdevice from the same FFmpeg family.
go get github.com/bstkhq/go-ffmpeg-ffiThe imported package is named ffmpeg:
import "github.com/bstkhq/go-ffmpeg-ffi"This project is based on ffgo but is not API-compatible with it. Do not use the
original github.com/obinnaokechukwu/ffgo import path or copy examples from the
archived documentation.
Configure the library location before ffmpeg.Init() or any high-level API call:
| Platform | Primary application-controlled location |
|---|---|
| Linux | LD_LIBRARY_PATH=/path/to/ffmpeg/lib |
| macOS | DYLD_LIBRARY_PATH=/path/to/ffmpeg/lib |
| Windows | Add the FFmpeg bin directory to PATH. |
| Android | Package unversioned libav*.so files in the APK/AAR native-library namespace. |
| iOS | Embed and sign FFmpeg frameworks, or link complete FFmpeg archives into the final process image. |
Do not mix libraries from different FFmpeg builds in one search directory. The loader validates the core and optional library-major tuple and rejects unknown or mixed families before accessing version-specific structures.
The optional C shim provides operations that cannot be expressed safely through
direct PureGo calls. Point FFMPEG_SHIM_DIR at a shim built against the same
FFmpeg family. A missing optional shim does not prevent core decoding; an
incompatible shim is rejected. Prebuilt desktop shims are release assets; their
manifest.json identifies the FFmpeg family and checksum. Select the matching
versioned directory before setting FFMPEG_SHIM_DIR. Source/build instructions
are in the shim README.
High-level constructors initialize FFmpeg automatically. Calling Init
explicitly gives startup failures a clear place in the application lifecycle:
if err := ffmpeg.Init(); err != nil {
log.Fatal(err)
}
log.Print(ffmpeg.Diagnose())Diagnose reports the OS and architecture, loaded FFmpeg versions, shim status,
and availability of scaling, resampling, filters, and devices. It reports
capabilities of the exact runtime build, not theoretical FFmpeg features.
package main
import (
"errors"
"io"
"log"
"github.com/bstkhq/go-ffmpeg-ffi"
)
func main() {
decoder, err := ffmpeg.NewDecoder("video.mp4", nil)
if err != nil {
log.Fatal(err)
}
defer decoder.Close()
if !decoder.HasVideo() {
log.Fatal(ffmpeg.ErrNoVideoStream)
}
stream := decoder.VideoStream()
log.Printf("%s %dx%d", stream.CodecID, stream.Width, stream.Height)
for {
frame, err := decoder.DecodeVideo()
switch {
case errors.Is(err, io.EOF):
return
case err != nil:
log.Fatal(err)
case frame.IsNil():
continue
}
log.Printf("frame pts=%d format=%d", frame.PTS(), frame.PixelFormat())
}
}DecodeVideo opens the selected video decoder on first use and returns
io.EOF after it is fully drained. Context variants are available for
cancellable open and decode operations.
Frames returned by DecodeVideo, DecodeAudio, and ReadFrame are borrowed
from the decoder. They remain valid only until the next decode/reuse operation
and must not be freed by the caller.
Use DecodeVideoCopy, DecodeAudioCopy, ReadFrameCopy, or Frame.Clone when
a frame must be retained. Those frames are owned by the caller and must be
released:
frame, err := decoder.DecodeVideoCopy()
if err != nil {
return err
}
defer frame.Free()Plane data returned by Frame.Data is also borrowed. Copy it before the frame
is reused or freed.
Hardware decoding uses the normal Decoder; it is not a separate decoder API.
An empty HWDecoderConfig asks FFmpeg for the compatible decoders and device
configurations compiled into the loaded libraries, ranks them for the current
platform, and falls back to software if none can be opened:
decoder, err := ffmpeg.NewDecoder("video.mp4", &ffmpeg.DecoderOptions{
Hardware: &ffmpeg.HWDecoderConfig{},
})Use HardwareAccelerationRequired when software fallback is unacceptable, or
set DeviceType, Device, or a borrowed HWDevice for explicit control.
Frames returned by the normal decode methods remain CPU-accessible; hardware
frames are transferred to system memory when the selected FFmpeg decoder
returns GPU-backed frames. Since some accelerators initialize lazily, required
mode can report ErrHardwareAccelerationUnavailable from the first decode call
even when OpenVideoDecoder succeeded.
Selection is lazy. VideoDecoderInfo reports pending before the video decoder
opens, selected after a hardware codec and device open, and active or
fallback after decoding establishes the actual output path. Always inspect
FallbackReason before making performance claims:
if err := decoder.OpenVideoDecoder(); err != nil {
return err
}
info := decoder.VideoDecoderInfo()
log.Printf("decoder=%s hardware=%s device=%s fallback=%s",
info.CodecName, info.HardwareState, info.HWDeviceName, info.FallbackReason)Automatic selection cannot add a backend omitted from the native FFmpeg build. For example, Android MediaCodec requires a MediaCodec-enabled FFmpeg build and a compatible codec/profile on the device.
Encoding, muxing, filters, capture, and hardware acceleration have less production coverage than playback. Check the installed FFmpeg capabilities and test the exact codec/container/device combination you ship.
Prefer Encoder.Flush() over using an empty frame as FFmpeg's flush marker.
Always handle the result of Encoder.Close(): it flushes delayed packets and
writes the output trailer, so discarding that error can hide an incomplete
output file.
if err := encoder.Flush(); err != nil {
return err
}
if err := encoder.Close(); err != nil {
return err
}Video and audio frames preserve an explicitly supplied PTS. When the PTS is
AV_NOPTS_VALUE, the encoder supplies the next frame or sample timestamp.
The Go module does not build an APK, AAR, IPA, or application-specific FFmpeg distribution. Those artifacts and their native licensing material belong to the consuming application.
These fixtures prove downstream packaging and binding without adding an Ebitengine dependency to go-ffmpeg-ffi itself.