luv

Workshop wiki

video.lisp

hal/video.lisp

system luv · 3 definitions · on GitHub

Video output for renderer readbacks: raw frames piped into ffmpeg.

The renderer already produces tightly packed rgba or BGRA bytes for PNG screenshots; a film is the same bytes written frame after frame into an ffmpeg child process reading rawvideo on stdin. ffmpeg owns pixel-format conversion, H.264 encoding, and the MP4 container, so no libav encoding binding is needed here -- the existing luv/libav system remains a decoder.

in-package#:luv
defunvideo-pixel-format-name
format

The ffmpeg rawvideo -pix_fmt for a renderer readback format.

ecaseformat
:rgba8-unorm:rgba8-unorm-srgb
"rgba"
:bgra8-unorm:bgra8-unorm-srgb
"bgra"
defuncall-with-video-encoder
functionpathnamewidthheight&key
frame-rate30
format:rgba8-unorm
constant-rate-factor18

Encode a video at pathname from frames function writes.

function receives one argument, a writer to call with each frame's packed pixel bytes in format at width by height. Frames become an H.264 MP4 at frame-rate via an ffmpeg subprocess; constant-rate-factor is x264's quality knob (lower is better, 18 is visually lossless). An odd source dimension is padded by one pixel because broadly playable yuv420p requires even dimensions. Returns pathname and the number of frames written.

check-typewidth
integer1
check-typeheight
integer1
ensure-directories-existpathname
let*
frame-bytes
frame-count0
error-output
make-string-output-stream
process
sb-ext:run-program"ffmpeg"
list"-hide_banner""-loglevel""error""-y""-f""rawvideo""-pix_fmt""-video_size"
formatnil"~Dx~D"widthheight
"-framerate"
formatnil"~D"frame-rate
"-i""-""-vf""pad=ceil(iw/2)*2:ceil(ih/2)*2""-c:v""libx264""-preset""medium""-crf"
formatnil"~D"constant-rate-factor
"-pix_fmt""yuv420p""-movflags""+faststart"
uiop:native-namestringpathname
:searcht:waitnil:input:stream:outputnil:errorerror-output
unwind-protect
handler-case
progn
funcallfunction
lambda
pixels
unless
=
lengthpixels
frame-bytes
error"Expected ~D frame bytes, got ~D."frame-bytes
lengthpixels
write-sequencepixels
sb-ext:process-inputprocess
incfframe-count
close
sb-ext:process-inputprocess
sb-ext:process-waitprocess
let
code
sb-ext:process-exit-codeprocess
unless
eqlcode0
error"ffmpeg exited with code ~A:~%~A"code
get-output-stream-stringerror-output
valuespathnameframe-count
sb-int:broken-pipe
ignore-errors
close
sb-ext:process-inputprocess
:abortt
sb-ext:process-waitprocess
error"ffmpeg stopped accepting frames (exit ~A):~%~A"
sb-ext:process-exit-codeprocess
get-output-stream-stringerror-output
when
sb-ext:process-alive-pprocess
close
sb-ext:process-inputprocess
:abortt
sb-ext:process-waitprocess
defmacrowith-video-encoder
writerpathnamewidthheight&restoptions
&bodybody

Evaluate body with writer bound as a local frame-writing function.

Call (writer pixels) once per frame; see call-with-video-encoder for PATHNAME, width, height, and OPTIONS.

let
function
gensym"WRITER"
`
call-with-video-encoder
lambda
flet
,writer
pixels
funcall,functionpixels
,@body
,pathname,width,height,@options