Skip to content

Filter graph config diagnostics

Mailing-list subject (draft): [RFC] libavfilter: return graph-config error detail to callers, not only via av_log

When avfilter_graph_config() fails, the reason is emitted with av_log() — e.g. Output pad "output1" … of split not connected to any destination — while the function returns a generic AVERROR(EINVAL). A caller only has the return code; the actionable detail has already gone to stderr.

For a language binding this bites hard. We build a graph from the user’s code, and when they make a recoverable mistake — a split whose second output is never consumed, an option a filter rejects, an unlinked pad — all we can hand back is “Invalid argument.” The text that says what to fix is on a log stream the binding doesn’t own and can’t attribute to a specific config() call.

You can see exactly this in the extension today: leave a split output unconsumed and the EncodingException reads avfilter_graph_config: Invalid argument, while the useful line lands on stderr where a PHP caller never sees it.

Why a binding can’t just capture the log

Section titled “Why a binding can’t just capture the log”
  • av_log_set_callback() is process-global. A library that hijacks it steals logging from the host application and anything else linking FFmpeg — unacceptable in a shared runtime.
  • Even with a callback, messages aren’t attributable to one config() call or scoped to one AVFilterGraph, and aren’t structured — you’d be buffering free text and guessing.
  • Swapping the global callback around each call isn’t thread-safe.

So this extension does a partial workaround: before calling config() it walks the graph and pre-detects the common case (an unconnected output pad) to throw a precise exception naming the filter, the pad, and the fix. That covers one failure mode; it doesn’t generalise to every config-time error.

  1. Error-string out-param — a sibling entry point, e.g. avfilter_graph_config2(AVFilterGraph *graph, void *log_ctx, char *errbuf, size_t errbuf_size), that writes the human-readable reason on failure. Smallest surface, no struct/ABI change, mirrors av_strerror. Leaves the existing function untouched.
  2. Per-graph last-error — store the last config diagnostic on the AVFilterGraph and expose a getter (or an AV_OPT-readable field). Scoped to the graph and thread-reasonable, but adds state to the struct.
  3. Scoped log capture — a per-AVFilterGraph log callback (not the global one), so a binding subscribes only to its own graph’s messages. Most general (captures all graph diagnostics), largest design.

Leaning: (1) for the narrow, low-risk win; (2) if a queryable error is preferred; (3) as the general direction.

  • The error/return plumbing in libavfilter/avfiltergraph.c (graph_check_validity() and where the “pad not connected” av_log originates) — confirm those paths already carry a string we can route to the caller.
  • Whether maintainers prefer a new function or an AVClass/AV_OPT field.
  • ABI/versioning: any addition must be additive (new function, or a new field at struct end) behind a LIBAVFILTER_VERSION bump — no change to existing signatures or layout.

To check before posting: search the archives for earlier requests to surface filtergraph config errors through the return path. The 2013 “Scripting RFC” on binding lavfi to other languages is the relevant backdrop (same as the expression variables proposal).

Seed/notes — not yet a list-ready patch. Filed so the workaround in the extension (the pre-config unconnected-pad scan) has a documented upstream end-state to aim at.

An Artisan Build project.

Built on FFmpeg — an independent binding, not affiliated with or endorsed by the FFmpeg project. Support FFmpeg.