/* SPDX-License-Identifier: BSD-2-Clause */ /* * ffmpeg_loader.c — runtime FFmpeg loader. */ #include "ffmpeg_loader.h" #include "log.h" #include #include #include #include /* * SONAME versions match the Kwiboo ffmpeg-v4l2-request-fourier * fork (FFmpeg 8.1) installed at the /opt/fourier prefix. The * fourier campaign's ld.so.conf.d/fourier.conf entry resolves * these sonames from /opt/fourier/lib via the ld cache, so * dlopen-by-soname works without LD_LIBRARY_PATH wrappers. * * Switched from Debian-stock soname 61/61/59 (FFmpeg 7.1.3) at * 2026-05-21 to land daedalus-fourier kernel substitution into * the production decode path via patches in the Kwiboo fork * (see daedalus-v4l2#11 substitution arc): we own the fork * source in marfrit-packages, so we can layer NEON-DSP * substitution patches there for libavcodec/aarch64/h264dsp_init * → daedalus_recipe_dispatch_* thunks. The Debian-stock 7.1.3 * is built outside the marfrit-packages source tree, which * would have made layering substitution patches awkward. * * Note: libavutil bumps soname 59 → 60 between FFmpeg 7.1 and * 8.1; libavformat + libavcodec each bump 61 → 62. The public * API surface the daemon uses (avcodec_send_packet / * receive_frame / AVCodecContext flags / AVFrame fields) is * stable across the bump. */ #define LIBAVFORMAT_SONAME "libavformat.so.62" #define LIBAVCODEC_SONAME "libavcodec.so.62" #define LIBAVUTIL_SONAME "libavutil.so.60" /* * Resolve a symbol from a dlopen'd handle. Logs the failure * and goto's `out_fail` if missing. Use only inside * ffmpeg_loader_init. * * The `*(void **) &loader->sym = dlsym(...)` form is the * POSIX-recommended workaround for the ISO C "function pointer * is not a void pointer" rule (POSIX.1-2017 dlsym(3p), RATIONALE). * POSIX requires the conversion to work; ISO C does not. */ #define RESOLVE(handle_field, soname, sym) \ do { \ *(void **) &loader->sym = \ dlsym(loader->handle_field, #sym); \ if (!loader->sym) { \ log_err("dlsym(%s, %s): %s", \ soname, #sym, dlerror()); \ goto out_fail; \ } \ } while (0) int ffmpeg_loader_init(struct ffmpeg_loader *loader) { memset(loader, 0, sizeof(*loader)); loader->libavformat = dlopen(LIBAVFORMAT_SONAME, RTLD_LAZY); if (!loader->libavformat) { log_err("dlopen(%s): %s", LIBAVFORMAT_SONAME, dlerror()); goto out_fail; } loader->libavcodec = dlopen(LIBAVCODEC_SONAME, RTLD_LAZY); if (!loader->libavcodec) { log_err("dlopen(%s): %s", LIBAVCODEC_SONAME, dlerror()); goto out_fail; } loader->libavutil = dlopen(LIBAVUTIL_SONAME, RTLD_LAZY); if (!loader->libavutil) { log_err("dlopen(%s): %s", LIBAVUTIL_SONAME, dlerror()); goto out_fail; } /* libavformat */ RESOLVE(libavformat, LIBAVFORMAT_SONAME, avformat_alloc_context); RESOLVE(libavformat, LIBAVFORMAT_SONAME, avformat_free_context); RESOLVE(libavformat, LIBAVFORMAT_SONAME, avformat_open_input); RESOLVE(libavformat, LIBAVFORMAT_SONAME, avformat_close_input); RESOLVE(libavformat, LIBAVFORMAT_SONAME, avformat_find_stream_info); RESOLVE(libavformat, LIBAVFORMAT_SONAME, av_find_best_stream); RESOLVE(libavformat, LIBAVFORMAT_SONAME, av_read_frame); RESOLVE(libavformat, LIBAVFORMAT_SONAME, avformat_version); /* libavcodec */ RESOLVE(libavcodec, LIBAVCODEC_SONAME, av_packet_alloc); RESOLVE(libavcodec, LIBAVCODEC_SONAME, av_packet_free); RESOLVE(libavcodec, LIBAVCODEC_SONAME, av_packet_unref); RESOLVE(libavcodec, LIBAVCODEC_SONAME, av_frame_alloc); RESOLVE(libavcodec, LIBAVCODEC_SONAME, av_frame_free); RESOLVE(libavcodec, LIBAVCODEC_SONAME, av_frame_unref); RESOLVE(libavcodec, LIBAVCODEC_SONAME, avcodec_find_decoder); RESOLVE(libavcodec, LIBAVCODEC_SONAME, avcodec_alloc_context3); RESOLVE(libavcodec, LIBAVCODEC_SONAME, avcodec_free_context); RESOLVE(libavcodec, LIBAVCODEC_SONAME, avcodec_parameters_to_context); RESOLVE(libavcodec, LIBAVCODEC_SONAME, avcodec_open2); RESOLVE(libavcodec, LIBAVCODEC_SONAME, avcodec_send_packet); RESOLVE(libavcodec, LIBAVCODEC_SONAME, avcodec_receive_frame); /* libavutil */ RESOLVE(libavutil, LIBAVUTIL_SONAME, av_log_set_level); RESOLVE(libavutil, LIBAVUTIL_SONAME, av_get_media_type_string); RESOLVE(libavutil, LIBAVUTIL_SONAME, av_version_info); RESOLVE(libavutil, LIBAVUTIL_SONAME, av_pix_fmt_desc_get); { unsigned int v = loader->avformat_version(); log_info("FFmpeg loaded: %s (libavformat %u.%u.%u)", loader->av_version_info(), (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff); } return 0; out_fail: ffmpeg_loader_cleanup(loader); return -1; } void ffmpeg_loader_cleanup(struct ffmpeg_loader *loader) { if (!loader) return; if (loader->libavformat) dlclose(loader->libavformat); if (loader->libavcodec) dlclose(loader->libavcodec); if (loader->libavutil) dlclose(loader->libavutil); memset(loader, 0, sizeof(*loader)); }