luv

Workshop wiki

ffi.lisp

libav/ffi.lisp

system luv/libav · 35 definitions · on GitHub

Loading FFmpeg, and the slice of its C ABI luv speaks directly.

The libraries come in together because they version together: libavutil owns AVFrame, libavcodec owns the decoders, libavformat owns the demuxers, libswscale owns pixel conversion, and FFmpeg only supports combinations from a single build. The groveled LIBAV*_VERSION_MAJOR constants are the compile-time half of that agreement; load-libav checks them against what the loaded libraries say at run time, because a mismatch here does not fail loudly at the call site -- it silently reads a field from the wrong offset.

in-package#:luv.libav
defmacrowith-libav-native-environment
&bodybody

Run body with the floating-point environment FFmpeg's own code expects.

FFmpeg computes with floats that raise invalid-operation and divide-by-zero as a matter of course -- probing stream timing alone will do it -- and SBCL traps those by default, so an unmasked call dies inside avformat_find_stream_info rather than returning an error luv could handle. This is the same masking luv's SDL and Vulkan boundaries use.

#+sbcl`
sb-int:with-float-traps-masked
:invalid:divide-by-zero:overflow:underflow:inexact
,@body
#+
anddarwin
notsbcl
`(float-features:with-float-traps-masked t ,@body)
#-
orsbcldarwin
`(progn ,@body)
define-conditionlibav-error
error
operation:initarg:operation:readerlibav-error-operation
code:initarg:code:readerlibav-error-code
:report
lambda
conditionstream
formatstream"FFmpeg operation ~S failed: ~A."
libav-error-operationcondition
define-conditionlibav-version-mismatch
error
library:initarg:library:readerlibav-version-mismatch-library
compiled:initarg:compiled:readerlibav-version-mismatch-compiled
loaded:initarg:loaded:readerlibav-version-mismatch-loaded
:report
lambda
conditionstream
formatstream

~A was groveled against major version ~D but the loaded ~ library reports major version ~D. The FFmpeg headers and ~ shared libraries in this environment are from different builds.

libav-version-mismatch-librarycondition
libav-version-mismatch-compiledcondition
libav-version-mismatch-loadedcondition

Loading.

The sonames are built from the groveled majors rather than written out, so the version this system was compiled against is stated exactly once. luv_FFMPEG_LIBDIR, which the Nix environment sets to the exact store path, is consulted before the platform search: on both Darwin and Linux CFFI explodes LD_library_PATH itself before consulting the system loader, so an unqualified soname would otherwise be free to find some other FFmpeg that happens to be installed.

defvar*libav-libraries*nil"The loaded libav* libraries, or NIL before anything asked for one."
defparameter*libav-default-directory*
uiop:getenv"LUV_FFMPEG_LIBDIR"

Pinned FFmpeg library directory captured when this system was loaded.

A saved standalone image retains this build-environment fallback so it can be launched directly. An explicit load-libav argument or the current process environment still takes precedence.

defunlibrary-sonames
stemmajor

Return the names to try for libSTEM at major version major, most exact first.

#+darwin (list (format nil "lib~A.~D.dylib" stem major) (format nil "lib~A.dylib" stem))#-darwin
list
formatnil"lib~A.so.~D"stemmajor
formatnil"lib~A.so"stem
defunload-libav
&optionaldirectory

Load libavutil, libavcodec, and libavformat, and check their versions.

directory is useful for builds which have not been installed. When it is NIL, luv_FFMPEG_LIBDIR is consulted before the platform soname search. Signals libav-version-mismatch when a loaded library disagrees with the headers this system was groveled against.

or*libav-libraries*
let*
override
ordirectory
uiop:getenv"LUV_FFMPEG_LIBDIR"
*libav-default-directory*
cffi:*foreign-library-directories*
ifoverride
cons
uiop:ensure-directory-pathnameoverride
cffi:*foreign-library-directories*
cffi:*foreign-library-directories*
setf*libav-libraries*
loopfor
stemmajor
in
list
list"avutil"+avutil-version-major+
list"avcodec"+avcodec-version-major+
list"avformat"+avformat-version-major+
list"swscale"+swscale-version-major+
collect
cffi:load-foreign-library
cons:or
*libav-libraries*
cffi:defcfun
"avutil_version"%avutil-version
:unsigned-int
cffi:defcfun
"avcodec_version"%avcodec-version
:unsigned-int
cffi:defcfun
"avformat_version"%avformat-version
:unsigned-int
cffi:defcfun
"swscale_version"%swscale-version
:unsigned-int
cffi:defcfun
"av_version_info"%av-version-info
:string
cffi:defcfun
"avutil_configuration"%avutil-configuration
:string
defunversion-triple
version

Split an AV_version_INT into its major, minor, and micro parts.

list
ldb
byte816
version
ldb
byte88
version
ldb
byte80
version
defuncheck-libav-versions

Signal unless every loaded library matches the headers we groveled.

AV-FRAME spells its arrays as a literal 8 because :COUNT cannot hold a constant; this is what keeps that literal honest.

assert
=8+data-pointer-count+
"AV_NUM_DATA_POINTERS is ~D, but AV-FRAME was written for 8."
+data-pointer-count+
loopfor
namecompiledreader
in
list
list"libavutil"+avutil-version-major+#'%avutil-version
list"libavcodec"+avcodec-version-major+#'%avcodec-version
list"libavformat"+avformat-version-major+#'%avformat-version
list"libswscale"+swscale-version-major+#'%swscale-version
forloaded=
ldb
byte816
funcallreader
unless
=compiledloaded
do
error'libav-version-mismatch:libraryname:compiledcompiled:loadedloaded
t
defunlibav-build

Return FFmpeg's own version string, such as "8.1.2".

%av-version-info
defunlibav-versions

Return an alist of library name to (major minor micro).

list
cons"libavutil"
version-triple
%avutil-version
cons"libavcodec"
version-triple
%avcodec-version
cons"libavformat"
version-triple
%avformat-version
cons"libswscale"
version-triple
%swscale-version
defunlibav-configuration

Return the ./configure line this FFmpeg was built with.

%avutil-configuration

Errors.

cffi:defcfun
"av_strerror"%av-strerror
:int
code:int
buffer:pointer
size:size
defunlibav-error-message
condition

Return FFmpeg's own description of condition's error code.

let
code
libav-error-codecondition
cffi:with-foreign-pointer
buffer256
if
minusp
%av-strerrorcodebuffer256
formatnil"unknown FFmpeg error ~D"code
cffi:foreign-string-to-lispbuffer
defuncheck-code
codeoperation

Return code, or signal libav-error when FFmpeg reported a failure.

if
minuspcode
error'libav-error:operationoperation:codecode
code

Formats and hardware devices.

The iteration and naming entry points are declared with :INT rather than the groveled enums on purpose. A CFFI enum translation errors on a value it has never heard of, and the set of hardware device types a given FFmpeg was built with is exactly the thing we are asking about -- so the answer comes back through FFmpeg's own name strings, and an unlisted type still arrives as a perfectly good keyword.

cffi:defcfun
"av_get_pix_fmt_name"%av-get-pix-fmt-name
:string
format:int
cffi:defcfun
"av_hwdevice_iterate_types"%av-hwdevice-iterate-types
:int
previous:int
cffi:defcfun
"av_hwdevice_get_type_name"%av-hwdevice-get-type-name
:string
type:int
cffi:defcfun
"avcodec_find_decoder_by_name"%avcodec-find-decoder-by-name
:pointer
name:string
defunpixel-format-name
format

Return FFmpeg's name for format, a PIXEL-FORMAT keyword or raw integer.

%av-get-pix-fmt-name
if
keywordpformat
cffi:foreign-enum-value'pixel-formatformat
format
defunname-keyword
name
andname
intern
string-upcase
substitute#\-#\_name
:keyword
defunhardware-device-types

Return the hardware device types this FFmpeg was built with, as keywords.

The list is whatever the library reports, so a type this system does not name in its HARDWARE-DEVICE-TYPE enumeration still appears, keyed by FFmpeg's own name for it.

loopwithtype=0do
setftype
%av-hwdevice-iterate-typestype
until
zeroptype
collect
name-keyword
%av-hwdevice-get-type-nametype
defunhardware-device-type-name

Return FFmpeg's name for type, a HARDWARE-DEVICE-TYPE keyword.

%av-hwdevice-get-type-name
cffi:foreign-enum-value'hardware-device-typetype
defundecoder-available-p
name

Return true when this FFmpeg has a decoder called name, such as "h264".

not
cffi:null-pointer-p
%avcodec-find-decoder-by-namename

Frames.

cffi:defcfun
"av_frame_alloc"%av-frame-alloc
:pointer
cffi:defcfun
"av_frame_free"%av-frame-free
:void
frame:pointer
cffi:defcfun
"av_frame_unref"%av-frame-unref
:void
frame:pointer
cffi:defcfun
"av_frame_get_buffer"%av-frame-get-buffer
:int
frame:pointer
alignment:int