{"id":51085578,"url":"https://github.com/thesyncim/goav","last_synced_at":"2026-06-23T21:32:32.437Z","repository":{"id":362785133,"uuid":"1260758211","full_name":"thesyncim/goav","owner":"thesyncim","description":"Pure-Go media workflow runtime for validated, inspectable, in-process audio/video pipelines","archived":false,"fork":false,"pushed_at":"2026-06-23T13:55:42.000Z","size":4220,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T14:11:49.300Z","etag":null,"topics":["audio","codecs","go","media","pure-go","realtime","rtp","streaming","video","webrtc"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/thesyncim/goav","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thesyncim.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T20:56:12.000Z","updated_at":"2026-06-23T13:56:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thesyncim/goav","commit_stats":null,"previous_names":["thesyncim/goav"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thesyncim/goav","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesyncim%2Fgoav","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesyncim%2Fgoav/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesyncim%2Fgoav/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesyncim%2Fgoav/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thesyncim","download_url":"https://codeload.github.com/thesyncim/goav/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesyncim%2Fgoav/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34708272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio","codecs","go","media","pure-go","realtime","rtp","streaming","video","webrtc"],"created_at":"2026-06-23T21:32:31.406Z","updated_at":"2026-06-23T21:32:32.422Z","avatar_url":"https://github.com/thesyncim.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# goav\n\n[![CI](https://github.com/thesyncim/goav/actions/workflows/ci.yml/badge.svg)](https://github.com/thesyncim/goav/actions/workflows/ci.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/thesyncim/goav.svg)](https://pkg.go.dev/github.com/thesyncim/goav)\n[![Go Version](https://img.shields.io/badge/go-1.26.4%2B-00ADD8)](go.mod)\n[![License](https://img.shields.io/github/license/thesyncim/goav)](LICENSE)\n[![Status](https://img.shields.io/badge/status-pre--v1-orange)](docs/ROADMAP.md)\n[![Release Notes](https://img.shields.io/badge/release_notes-CHANGELOG-blue)](CHANGELOG.md)\n\nBuild media workflows that your Go application can understand, validate, and\nchange while they run.\n\n`goav` is for product code where audio/video is not just a shell command on the\nside. You describe a recipe from input to destination, ask goav to validate the\nshape before resources open, then run a task that can emit events, snapshots,\nstats, controls, and live branch changes.\n\nIt is pure Go at the core: no cgo requirement, no FFmpeg or GStreamer process\nstarted behind your back, and no global adapter table. If the adapters you use\nare pure Go, `CGO_ENABLED=0` can build the whole thing into one binary.\n\nUse goav when you want:\n\n- a recording, preview, diagnostic, or live-room path inside a Go service;\n- structured build errors instead of parsing stderr;\n- runtime attach, detach, rebranch, pause/resume, bitrate, and keyframe control;\n- custom sources, sinks, codecs, filters, joins, and control hosts that are\n  registered per runtime.\n\nSkip it when you mainly need a desktop playback stack, hardware codec discovery,\nor the breadth of a mature multimedia framework.\n\n## Install\n\n```sh\ngo get github.com/thesyncim/goav\n```\n\n## 30-Second Examples\n\nStart with one packet-preserving recording:\n\n```go\nreturn goav.From(goav.FileInput(\"input.ivf\", in)).\n    Copy().\n    To(goav.File(\"recording.ivf\", out)).\n    Run(ctx)\n```\n\nThat is the core idea: choose an input, choose the stream work, choose a\ndestination, run the task. Bigger recipes keep the same shape.\n\nThe public vocabulary is deliberately small:\n\n- **Input** is a file, URI, transport provider, generated source, or\n  application-owned source.\n- **Stream** is `.Audio()`, `.Video()`, or a specific stream selector.\n- **Operation** is ordered work such as `.Decode()`, `.Copy()`, `.Resize()`,\n  `.Resample()`, `.Do(stage)`, `.Encode(codec)`, or `.Tap(...)`.\n- **Tap** names a point for later use. `goav.FrameTap(...)` and\n  `goav.PacketTap(...)` make the media domain explicit.\n- **Branch** is another path from a stream or tap. Use `Branches(...)` with\n  `goav.Branch(...)` when one stream needs several outcomes.\n- **Destination** is where media goes: `goav.File(...)`, `goav.Writer(...)`,\n  `goav.Sink(...)`, a URI, an object destination, or a shared mux/sink group.\n- **Flow** is reusable operation text with no source or destination; build one\n  with `goav.Flow(...)`.\n- **Task** is the running work: events, watch, snapshots, stats, control,\n  attach, rebranch, and detach.\n\nReuse the same destination value when several branches should feed one mux,\none sink group, or one transactional writer.\n\n## Common Recipes\n\n| I need to... | Recipe sketch | Go deeper |\n|---|---|---|\n| Record packets without decoding | `From(input).Copy().To(goav.File(...))` | [Use cases](docs/USE_CASES.md) |\n| Record and preview from one stream | `.Branches(goav.Branch(\"archive\").To(...), goav.Branch(\"preview\").To(...))` | [Use cases](docs/USE_CASES.md) |\n| Decode, filter, and encode again | `.Decode().Resize(...)` or `.Resample(...)`, then `.Encode(codec)` | [Operations](docs/OPERATIONS.md) |\n| Attach a live room participant | `Task.Attach(ctx, goav.Branch(...).From(input.Stream(track)))` | [Dynamic audio room](examples/dynamic-audio-room) |\n| Add a meter or diagnostic sink | `goav.FrameTap(...)`, then attach a branch ending in `goav.Sink(...)` | [Control plane](docs/CONTROL_PLANE.md) |\n| Wrap app-owned media | `goav.Source(...)` or `goav.Input(provider)` | [Extension cookbook](docs/EXTENSION_COOKBOOK.md) |\n| Add a codec, filter, muxer, or control host | `goav.New(...)` with the relevant extension option | [Adapter authoring](docs/ADAPTER_AUTHORING.md) |\n\n## Expandable Examples\n\nOpen the examples when you want to see the recipe shape. They stay on the same\ngrammar as the 30-second examples, but use live sources, runtime branches, taps,\nand control boundaries.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eLive camera track: archive steadily, keep preview low-latency\u003c/strong\u003e\u003c/summary\u003e\n\nFeed packets from the transport edge into one task. The archive branch holds and\ndrains; the preview branch uses the same room timeline but can shed late packets\ninstead of making the recorder wait.\n\n```go\ncameraPackets := make(chan *av.Packet)\nroomSync := goav.Sync(\"room\", goav.SyncTolerance(20*time.Millisecond))\npreviewSync := goav.Sync(\"room\",\n    goav.SyncTolerance(20*time.Millisecond),\n    goav.SyncDropLate(),\n)\nroomCamera := goav.Source(\"room-camera\",\n    shape.Packet(av.MediaVideo, av.CodecVP8, shape.Realtime(true)),\n    func(ctx context.Context, push goav.SourcePush) error {\n        for {\n            select {\n            case \u003c-ctx.Done():\n                return ctx.Err()\n            case packet, ok := \u003c-cameraPackets:\n                if !ok {\n                    return push.EOS(\"room-camera\")\n                }\n                if _, err := push.Packet(packet); err != nil {\n                    return err\n                }\n            }\n        }\n    },\n    goav.Codec(codec.VP8()),\n)\npreviewTrack := goav.Sink(goav.SinkFunc(\"preview-track\", func(context.Context, goav.Message) error {\n    return nil\n}))\n\n_, err := goav.From(roomCamera).\n    Video().\n    Copy().\n    Sync(roomSync).\n    Branches(\n        goav.Branch(\"archive\").\n            Sync(roomSync).\n            To(goav.File(\"archive.ivf\", out)),\n        goav.Branch(\"preview\").\n            Sync(previewSync).\n            To(previewTrack),\n    ).\n    Build(ctx)\nreturn err\n```\n\nHere `cameraPackets` is owned by your RTP/WebRTC edge and should carry\nRTP-derived PTS values. goav only sees a typed live source. Run the\ndeterministic fixture in\n[examples/dynamic-audio-room](examples/dynamic-audio-room), or the browser\ndemo in [examples/gio-webrtc-showcase](examples/gio-webrtc-showcase).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eRuntime diagnostics: attach a meter after the task starts\u003c/strong\u003e\u003c/summary\u003e\n\nPut a typed tap in the main recipe, then attach a support branch only when you\nneed it.\n\n```go\nlevels := goav.Sink(goav.SinkFunc(\"levels\", func(context.Context, goav.Message) error {\n    return nil\n}))\n\ntask, err := goav.From(goav.FileInput(\"input.ivf\", in)).\n    Video().\n    Decode().\n    Tap(goav.FrameTap(\"video.frames\")).\n    To(goav.Sink(goav.SinkFunc(\"archive\", func(context.Context, goav.Message) error {\n        return nil\n    }))).\n    Build(ctx)\nif err != nil {\n    return err\n}\ndefer task.Close()\n\n_, err = task.Attach(ctx, goav.Branch(\"support-meter\").\n    From(goav.FrameTap(\"video.frames\")).\n    To(levels))\nreturn err\n```\n\nThe diagnostic branch has its own lifecycle, so it can be watched, drained, or\naborted without rebuilding the archive path. The control-plane flow is covered\nin [docs/CONTROL_PLANE.md](docs/CONTROL_PLANE.md).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eMedia-time rebranch: replace work without a visible gap\u003c/strong\u003e\u003c/summary\u003e\n\nOpen the replacement branch first, then switch at a media boundary. This example\nuses a file as a repeatable source because the idea is branch rotation, not live\ntrack discovery. If the replacement fails to attach, the old branch keeps\nrunning.\n\n```go\ntask, err := goav.From(goav.FileInput(\"input.ivf\", in)).\n    Video().\n    Copy().\n    Tap(goav.PacketTap(\"video.packets\")).\n    To(goav.File(\"live.ivf\", out)).\n    Build(ctx)\nif err != nil {\n    return err\n}\ndefer task.Close()\n\nrec, err := task.Attach(ctx, goav.Branch(\"rec\").\n    From(goav.PacketTap(\"video.packets\")).\n    Copy().\n    To(goav.File(\"part-001.ivf\", io.Discard)))\nif err != nil {\n    return err\n}\n\n_, err = rec.Rebranch(ctx,\n    goav.Branch(\"rec\").\n        From(goav.PacketTap(\"video.packets\")).\n        Copy().\n        To(goav.File(\"part-002.ivf\", io.Discard)),\n    goav.SwitchAt(goav.AtMediaTime(30*time.Second)),\n    goav.KeepOldOnFailure(),\n    goav.DrainOldBranch(),\n)\nreturn err\n```\n\nUse this for preview rungs, diagnostics, or recording rotation when a branch\nneeds to change shape while the task keeps running.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDynamic WebRTC/RTP tracks: attach branches as streams appear\u003c/strong\u003e\u003c/summary\u003e\n\nTransport code stays at the edge. It announces track lifecycle events and pushes\npackets; the runtime rule attaches the same branch recipe whenever a matching\nstream appears.\n\n```go\nroomPackets := make(chan *av.Packet)\ncamera := av.Stream{\n    ID: \"camera\", Type: av.MediaVideo, TimeBase: av.RTPTimeBase(90000),\n    Codec: av.CodecParameters{ID: av.CodecVP8, Type: av.MediaVideo, ClockRate: 90000},\n}\ntransport := goav.Source(\"webrtc-room\",\n    shape.Packet(av.MediaVideo, av.CodecVP8, shape.Realtime(true)),\n    func(ctx context.Context, push goav.SourcePush) error {\n        announced := camera\n        if _, err := push.Event(av.Event{Type: av.EventStreamAdded, Stream: \u0026announced}); err != nil {\n            return err\n        }\n        for {\n            select {\n            case \u003c-ctx.Done():\n                return ctx.Err()\n            case packet, ok := \u003c-roomPackets:\n                if !ok {\n                    if _, err := push.Event(av.Event{Type: av.EventStreamRemoved, StreamID: camera.ID}); err != nil {\n                        return err\n                    }\n                    return push.EOS(camera.ID)\n                }\n                if packet == nil {\n                    continue\n                }\n                packet.StreamID = camera.ID\n                packet.Type = av.MediaVideo\n                if _, err := push.Packet(packet); err != nil {\n                    return err\n                }\n            }\n        }\n    },\n    goav.Codec(codec.VP8()),\n)\n\n_, err := goav.From(transport).\n    OnStream(\n        goav.MatchStreamID(\"camera\"),\n        goav.Branch(\"record-camera\").\n            Sync(goav.Sync(\"room\", goav.SyncTolerance(20*time.Millisecond))).\n            Copy().\n            To(goav.File(\"camera.ivf\", out)),\n        goav.OnRemove(goav.DrainBranch()),\n    ).\n    Build(ctx)\nreturn err\n```\n\nThe source announces the track before media, keeps packet routing on the same\nstream id, and removes the track before EOS. Late media still becomes a\nvalidated branch of the same task. Start with\n[docs/RTP_WEBRTC.md](docs/RTP_WEBRTC.md), then try\n[examples/gio-webrtc-showcase](examples/gio-webrtc-showcase).\n\n\u003c/details\u003e\n\n## Why goav\n\nMost media tools make the media graph powerful but distant: you build strings,\nstart a process, watch logs, and hope runtime state lines up with what your app\nthinks is happening. goav takes the opposite bet. The recipe is ordinary Go\ndata, the planner can explain failures before work starts, and the running task\nis still addressable by your application.\n\nThat matters when a production room needs one more recorder, a preview branch\nmust drop late frames without stalling archive output, or a support path needs\nto attach a meter to a typed tap for ten seconds and then disappear cleanly.\n\n| If you would normally reach for... | goav is the better fit when... |\n|---|---|\n| An FFmpeg command | you need typed validation, in-process events, or live controls. |\n| A GStreamer graph | you want a smaller Go-first recipe language with explicit extension seams. |\n| Pion glue code | RTP/WebRTC is only the ingress; you also need recording, transforms, muxing, and sinks. |\n| A custom in-house media runner | you want lifecycle, errors, stats, controls, and tests without inventing them again. |\n\n## Capability Matrix\n\n| Area | In tree today | What that means in practice |\n|---|---|---|\n| Core runtime | Recipes, task lifecycle, events, watch, snapshots, stats, attach, detach, rebranch, controls | Build one validated task, then inspect and change it while it runs. |\n| Live rooms | Dynamic stream rules, branch-local sync policies, detach outcomes, media-time rebranch | Record branches can stay steady while preview or diagnostics adapt. |\n| Containers | IVF, Annex B, Matroska, WebM | Current packet-recording and RTP/WebRTC examples work; more formats plug in through `format`. |\n| Codecs | Opus, VP8, VP9, AV1 encode/decode verticals; AAC-LC and H264 receive/decode active | Common realtime paths are present; some encode paths stay descriptor-only until backends are enabled. |\n| Transforms | Resize, resample, and custom frame stages | Conversion happens only when the recipe allows it. |\n| Transports | RTP and WebRTC in nested modules | Pion stays out of the root module and lives where transport code needs it. |\n| Extension points | Sources, destinations, codecs, formats, filters, stages, joins, control hosts, tests | Your application can add capability without changing global process state. |\n\n## Stability Matrix\n\ngoav is pre-v1, but the important surfaces are pinned so accidental API growth\nfails tests.\n\n| Surface | Tier | What that means |\n|---|---|---|\n| Recipe grammar, `Task`, structured errors, `plan`, `snapshot`, `lifecycle`, `shape`, `flow`, `av` vocabulary | Tier A | Stable enough to build against; changes are deliberate and test-pinned. |\n| Provider, codec, format, filter, control-host, custom-stage, custom-join, and testing seams | Tier B | Extension points are documented and may grow as capabilities grow. |\n| Expert graph and low-level components | Tier C | Supported for advanced composition, but not the first API to learn. |\n| Performance claims | Evidence based | Allocation pins and benchmarks are real; broad tail-latency and soak claims are not made yet. |\n| Release status | Pre-v1 | The remaining gate is the maintainer release decision and compatibility note; see [Roadmap](docs/ROADMAP.md). |\n\n## Deep Dives\n\n| Topic | Start here |\n|---|---|\n| Recipes, branches, and live-room patterns | [Use cases](docs/USE_CASES.md) |\n| Operation rules and structured errors | [Operations](docs/OPERATIONS.md), [Errors](docs/ERRORS.md), [Error catalog](docs/ERROR_CATALOG.md) |\n| Runtime control and socket hosts | [Control plane](docs/CONTROL_PLANE.md), [control-plane host example](examples/control-plane-host) |\n| Browser and dynamic-media demos | [Dynamic audio room](examples/dynamic-audio-room), [Gio WebRTC showcase](examples/gio-webrtc-showcase), [WebRTC runtime ladder](examples/webrtc-runtime-ladder) |\n| Extension authoring | [Extension cookbook](docs/EXTENSION_COOKBOOK.md), [Adapter authoring](docs/ADAPTER_AUTHORING.md), [Adapters](docs/ADAPTERS.md) |\n| Copyable extension modules | [custom source](examples/custom-source), [provider source](examples/provider-source), [custom destination](examples/custom-destination), [custom filter](examples/custom-filter), [transactional writer](examples/transactional-writer), [custom codec](examples/custom-codec), [custom join](examples/custom-join) |\n| API governance and composition rules | [API surface](docs/API_SURFACE.md), [Composability laws](docs/COMPOSABILITY_LAWS.md) |\n| Reusable components | [Components](docs/COMPONENTS.md) |\n| Performance methodology | [Performance](docs/PERFORMANCE.md) |\n| Repository trust and release process | [Repository trust](docs/REPOSITORY_TRUST.md), [Releasing](docs/RELEASING.md), [Compatibility](docs/COMPATIBILITY.md) |\n| V1 readiness | [V1 credibility audit](docs/V1_CREDIBILITY_AUDIT.md), [Roadmap](docs/ROADMAP.md) |\n| FFmpeg, GStreamer, and framework comparison | [GStreamer alternative](docs/GSTREAMER_ALTERNATIVE.md) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthesyncim%2Fgoav","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthesyncim%2Fgoav","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthesyncim%2Fgoav/lists"}