contrib/avcodec: in-process video decode, plus the CI wiring to actually gate it - #1472
Merged
Conversation
Aim #1 on aether-ui's video roadmap. A thin FFmpeg veneer following contrib/sqlite exactly: C shim + module.ae + a catalogue entry with a pkg-config probe, nothing vendored, user programs link -lavcodec -lavformat -lavutil -lswscale via aether.toml. Before, aether-ui's video_frame spawned ffmpeg to transcode a whole clip to raw RGBA on disk and fs.pread'd frames back: 27.6 MB for 6s of 320x240, ~1.5 GB per minute of 1080p, and NO workaround at all for a live source (camera, network stream) since there is no file to pread. Now: 27.6 MB -> 0. Measured 300 frames of 640x480 decoded in 0.426s including compile time. Surface is deliberately narrow -- open, next frame as packed RGBA8888, close. Video only; audio, seeking and stream selection are future work and none are needed to feed a renderer. Two ways to take a frame, mirroring sqlite's blob accessors: next_frame allocates a fresh owned string (simple, fine at small sizes); next_frame_into writes into a caller-owned buffer, allocating nothing per frame -- at 1080p30 that is 250 MB/s of churn avoided. fps() returns a RATIO rather than a float because 30000/1001 does not survive a float round-trip, and a presentation-timestamp model wants the exact value. The test's undersized-buffer assertion took two goes, and the first version is worth recording: it ran the decoder to EOF first, where next_frame_into returns 0 whatever the capacity -- so it passed with BOTH capacity guards deleted. Rewritten against a fresh decoder it catches the real thing: the sabotaged build returned m2=12288, writing 12 KB into a 16-byte allocation. It now also checks the decoder still works afterwards, so a refusal cannot silently consume the frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ly runs
The avcodec entry added alongside the module could never pass. `make
contrib-check` reported:
FAIL avcodec/decode (build)
collect2: error: ld returned 1 exit status
undefined reference to `avcodec_receive_frame'
...on a box with all four FFmpeg dev libraries installed. The runner builds each
test with `ae build --extra <shim.c>`, which compiles the shim but has NO way to
pass -l flags, so any module backed by a system library compiles and then dies
at link. The entry was wired in but structurally incapable of running.
The module itself is fine — this is purely the CI wiring. Proven by building the
same test through an aether.toml workspace, where all six assertions pass:
open + geometry, frame_bytes, fps ratio 10/1, a full RGBA frame, 20 frames
decoded to EOF via the reused buffer, and the undersized-buffer refusal leaving
the decoder usable.
Added an optional fifth column naming the pkg-config modules a test must link
against. When set, the runner stages an aether.toml workspace carrying
link_flags — the same shape tests/integration/sqlite_roundtrip already uses,
which is where ae's get_link_flags() picks them up — and SKIPS the entry when
pkg-config cannot find the modules, since an absent FFmpeg is a provisioning gap
on the box rather than a code defect.
Both paths verified here:
with FFmpeg: PASS avcodec/decode (run) + all six existing tests
without: SKIP avcodec/decode (pkg-config: ... not found)
Note contrib/sqlite has the same shape and is NOT in this table; it is covered
by tests/integration/sqlite_roundtrip instead. Worth folding in later so contrib
runtime coverage lives in one place, but that is a separate change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… avcodec contrib/sqlite is covered by tests/integration/sqlite_roundtrip/, not by .github/scripts/contrib_check.sh — so contrib runtime coverage lives in two places with different shapes and nothing says which a new module should use. This surfaced fixing the avcodec entry: it had been added to contrib_check.sh but could never pass, because the runner builds with `ae build --extra shim.c`, which compiles a C shim but cannot pass -l flags. Any module backed by a system library compiled and then died at link. The fix taught contrib_check.sh a fifth column naming pkg-config modules, staging an aether.toml workspace with link_flags — which turns out to be exactly what sqlite_roundtrip had been doing by hand in its own shell driver all along. So the two mechanisms now overlap, and the table can do generically what the shell driver does specifically. Parked rather than done: sqlite IS tested today, consolidating means rewriting a working test for no new coverage, and it needs a contrib/sqlite/test_sqlite.ae that does not exist yet (probe.ae is written for the workspace harness). Worth doing when someone next adds a native-backed contrib module and has to pick between the two patterns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
contrib/avcodec— in-process FFmpeg video decode, so a renderer no longer has to transcode a whole clip to disk first. Aim #1 on aether-ui's video roadmap.The module is Paul's work (commit 1); commits 2–3 are the shaping-up.
What the module does
A thin FFmpeg veneer following
contrib/sqlite: C shim +module.ae+ a catalogue entry with a pkg-config probe. Nothing vendored — user programs link-lavcodec -lavformat -lavutil -lswscalevia theiraether.toml.Before, aether-ui's
video_framespawnedffmpegto transcode a clip to raw RGBA on disk andfs.pread'd frames back: 27.6 MB for 6s of 320×240, ~1.5 GB per minute of 1080p, and no workaround at all for a live source (camera, network stream) since there is no file to pread. Now 27.6 MB → 0.Surface is deliberately narrow — open, next frame as packed RGBA8888, close. Two ways to take a frame, mirroring sqlite's blob accessors:
next_frameallocates a fresh owned string;next_frame_intowrites into a caller-owned buffer, allocating nothing per frame (at 1080p30 that is 250 MB/s of churn avoided).fps()returns a ratio, not a float, because 30000/1001 does not survive a float round-trip.The CI gate could never have passed
The module arrived with a good test — self-generating fixture via
ffmpeg, clean SKIP when the binary is absent, and a sharp comment noting that the undersized-buffer assertion passed even with both capacity guards deleted (because the decoder was already at EOF), so it opens a fresh decoder to test a real refusal.But its
contrib_check.shentry could not run. On a box with all four FFmpeg dev libraries installed:The runner builds each test with
ae build --extra <shim.c>, which compiles the shim but has no way to pass-lflags. So any module backed by a system library compiles and then dies at link. The entry was wired in but structurally incapable of running.Fix: an optional fifth column naming the pkg-config modules a test must link against. When set, the runner stages an
aether.tomlworkspace carryinglink_flags— the same shapetests/integration/sqlite_roundtripalready uses, which is whereae'sget_link_flags()picks them up — and SKIPs when pkg-config cannot find them, since an absent FFmpeg is a provisioning gap rather than a code defect.I first tried
@link(...)inmodule.ae(following sqlite's declaration). That does not work for this path and was reverted —// aether-link:is emitted by codegen as advice for a downstream build, not consumed byae build.Verification
Both paths, on this box:
The module itself passes all six assertions — open + geometry,
frame_bytes= w·h·4, fps ratio 10/1, a full RGBA frame, 20 frames decoded to EOF via the reused buffer, and the undersized-buffer refusal leaving the decoder usable.make ci— C suite 230/230,.ae975/977. Both failures are pre-existing and unrelated:integration_http_server_h2(diagnosed inpesky_bug.md— an h2 connection-reuse bug, not the "50-stream" concurrency issue its name suggests) andhttp_middleware_d1, which passes in isolation and is the known load-sensitive flake.Also
TODO.mdgains a parked item:contrib/sqliteis covered bytests/integration/sqlite_roundtrip/rather thancontrib_check.sh, so contrib runtime coverage lives in two places with different shapes. The fix above means the table can now do generically what that shell driver does by hand — worth consolidating, but it rewrites a working test for no new coverage, so it is parked with the concrete next step written down.🤖 Generated with Claude Code