Media pipelines in pure Go. Decode, transform, encode, and mux inside your service:
the core needs no cgo, no FFmpeg, and no system libraries.
You describe media work as plain Go values, and goav plans it, builds the graph, and runs it. The codecs and containers are pure Go (VP8, VP9, AV1, Opus; WebM, Matroska, MP4), so your service still ships as one static binary.
go get github.com/thesyncim/goav@latest # Go 1.27+
GOEXPERIMENT=simd go build ./... # always: enables the SIMD codec pathsBuild, test, and run with GOEXPERIMENT=simd. It switches the codecs onto Go 1.27's
portable SIMD paths; without it they fall back to scalar code. On one machine (Apple M4 Max, 128x96 test clips) that cut codec benchmark time by roughly half; treat it as a direction, not a guarantee (measurements and scope).
package main
import (
"context"
"log"
"os"
"github.com/thesyncim/goav"
"github.com/thesyncim/goav/bundle"
"github.com/thesyncim/goav/codec"
)
// Scale a WebM to 720p and re-encode it as VP9.
func main() {
src, err := os.Open("in.webm")
if err != nil {
log.Fatal(err)
}
dst, err := os.Create("out.webm")
if err != nil {
log.Fatal(err)
}
video := goav.FileInput("in.webm", src).Video().
Decode().
Resize(1280, 720).
Encode(codec.VP9(codec.Bitrate(2_000_000)))
if err := bundle.Run(context.Background(), video.To(goav.Write("out.webm", dst))); err != nil {
log.Fatal(err)
}
}goav.FileInput takes any io.Reader and goav.Write any io.Writer; bundle.Run closes
the writer when the run ends.
No WebM handy? Make a test clip with the CLI.
GOEXPERIMENT=simd go run github.com/thesyncim/goav/cmd/goav@latest run \
'testsrc video size=1920x1080 duration=2s ! encode vp9 ! file in.webm
testsrc audio duration=2s ! encode opus ! file in.webm'Every operation returns a new goav.Stream, so a stream you use twice forks, and the work
before the fork runs once. Here one decode feeds two renditions, and one Opus encode of the
input's audio track is muxed into both files:
in := goav.FileInput("in.webm", src)
video := in.Video().Decode()
audio := in.Audio().Decode().Encode(codec.Opus())
hd, sd := goav.Write("hd.webm", hdFile), goav.Write("sd.webm", sdFile)
err := bundle.Run(ctx,
video.Resize(1280, 720).Encode(codec.VP9()).As("hd").To(hd),
video.Resize(640, 360).Encode(codec.VP9()).As("sd").To(sd),
audio.To(hd, sd),
)stream.To(...) makes an Output. bundle.Run runs Outputs together with the bundled
codecs and containers; a runtime of your own runs them the same way: rt.Run(ctx, outputs...).
The same recipe as text, for a command line, a config file, or a control socket. It builds the
identical graph (launch, CLI):
GOEXPERIMENT=simd go install github.com/thesyncim/goav/cmd/goav@latest
goav run 'v := file in.webm ! video ! decode
v ! resize 1280x720 ! encode vp9 ! as hd ! file hd.webm
v ! resize 640x360 ! encode vp9 ! as sd ! file sd.webm
file in.webm ! audio ! decode ! encode opus ! file hd.webm, file sd.webm'A recipe is data until it runs. bundle.Describe(outputs...) returns the exact graph a run
would build, without opening anything, and graphrender.RenderURI(spec, "goav:graph") prints
it. The quick start's nodes:
pipeline goav
source in.webm [demux, protocol=file]
stage select-video [select, type=video]
stage decode-video [packets -> frames, type=video]
stage resize-video [resize, 1280x720, exact]
stage encode-video [frames -> packets, codec=vp9, backend=govpx (go)]
stage out.webm [mux, protocol=file]
A mistake comes back before anything opens, as a *goav.BuildError with a stable code and
the fix. Leave out the encoder and you get:
goav: cannot build job for video: decoded frames cannot be written to a muxed output without an encoder
Suggestions:
- call .Encode(codec.VP8(...)), .Encode(codec.VP9(...)), or .Encode(codec.AV1(...)) before .To(goav.Write(...))
- send decoded frames to goav.Sink(...)
- leave out .Decode() to copy the packets: input.Video().To(output)
See Errors for matching on codes; graphrender also renders
DOT and Mermaid.
| Decode / demux | Encode / mux | |
|---|---|---|
| Video | VP8, VP9, AV1, H.264 (-tags goav_goh264) |
VP8, VP9, AV1 |
| Audio | Opus | Opus (in WebM, Matroska, or MP4) |
| Containers | Matroska, WebM, IVF, MP4 | Matroska, WebM, MP4 (progressive or fragmented), IVF, Annex B (H.264) |
Plus Resize and Resample. bundle registers all of these; goav.New(...) builds a
runtime with only the adapters you choose, and there is no global registry.
AAC comes from libcodec (AudioToolbox, macOS) or the opt-in gplcodec
(pure Go, arm64 only, GPL). Everything bundle links is permissive, except LGPL H.264 under
its tag; see licenses.
| Need | Shape |
|---|---|
| Remux without decoding | in.To(goav.Write("out.mkv", w)) |
| Audio and video in one file | pass in.Video()…To(out) and in.Audio()…To(out) to one Run |
| Frames into your code | in.Video().Decode().To(goav.Sink(component.SinkFunc("frames", fn))) |
| Mix, composite, switch | goav.Mix(a, b), goav.Composite(cam, goav.At(screen.Resize(480, 270), 1420, 790)), goav.Select(a, b) |
| Package for HLS | goav.Custom("live", hls.New("live.m3u8", hls.Dir(dir), hls.Window(6))); hls.NewLadder for renditions, hls.Parts with an hls.Server for LL-HLS, or goav run --serve :8080 '... ! hls live.m3u8 parts=250ms' |
| Change a running task | task.Attach(ctx, task.Tap("live").Encode(codec.VP8()).To(preview)) |
| Test a pipeline | goavtest: deterministic sources, a recording sink, a fake clock |
RTP and WebRTC, in and out, live in their own modules (RTP & WebRTC), and
your own sources, codecs, and sinks plug in through small interfaces
(Extension cookbook). More in Use cases,
Operations, and examples/, including a browser
WebRTC ladder and a native Gio control room.
- One clock per task. Pacing, playout, pause and resume, seek, and rate share one timeline. Each fork can backpressure or drop on its own, and every drop is counted. See Flow control.
- Change it while it runs. Attach and detach outputs, switch inputs, and drive it all over a control socket (Control plane).
- Fast without assembly. Graph, codec, and filter stages allocate nothing per message,
and
GOEXPERIMENT=simdenables Go 1.27 SIMD codec paths (faster in our 128x96 microbenchmarks on one machine; not a general claim). See Performance.
| Module | Adds |
|---|---|
github.com/thesyncim/goav |
recipes, planner, runtime; bundled adapters in bundle |
goav/rtpav |
RTP in and out: jitter buffer, (de)packetizers, RTCP feedback (Pion) |
goav/webrtcav |
WebRTC tracks in and out, WHEP and WHIP (Pion) |
goav/playoutav |
scheduled packet, frame, and event playout |
goav/player |
a playback window, synced to the speaker: play in programs, goav play FILE |
goav/libcodec |
codecs in shared libraries and OS frameworks, no cgo: VideoToolbox, AudioToolbox, OpenH264 |
goav/gplcodec |
opt-in copyleft codecs: gplcodec.AAC(), a pure-Go AAC-LC decoder (GPL; your binary becomes GPL) |
goav/cmd/goav |
the goav CLI: run, describe, explain, play, ctl (CLI) |
goav/goavtest/expect |
go-cmp assertions for pipeline tests |
The core module never pulls in Pion.
Pre-v1: breaking changes can land until the first tag. See V1 scope and
the roadmap. Not related to github.com/giorgisio/goav, a cgo binding to FFmpeg.
All docs · Architecture · Adapter authoring · RTP & WebRTC · vs GStreamer · Changelog · Contributing · Security