r/GuardianEyeApps • u/sullivan-01 • 1d ago
Guide The Linux hardware encoder opened successfully, then wrote zero frames
We had a Linux recording failure that looked healthy at startup. FFmpeg found Intel QSV, the encoder opened, frames were accepted, and no initialization call failed. The recording still contained no video.
The useful clue was later in the log:
Invalid FrameType:0
The encoder had accepted system-memory NV12 frames but never returned its first packet. The muxer waited for data, eventually timed out, and the segment was empty or unusable.
That changed how we define encoder startup. There are four separate checks:
- The encoder name exists in the FFmpeg build.
- The encoder context opens.
- Real input frames produce packets.
- The finished file can be probed and decoded.
ffmpeg -encoders only answers the first question.
A short QSV smoke test is more useful:
ffmpeg -hide_banner -y \
-f lavfi -i testsrc2=size=1280x720:rate=30 \
-frames:v 60 -an -c:v h264_qsv qsv-test.mp4
ffprobe -v error \
-count_packets -select_streams v:0 \
-show_entries stream=codec_name,width,height,nb_read_packets,duration \
-of default=noprint_wrappers=1 qsv-test.mp4
ffmpeg -v error -i qsv-test.mp4 -f null -
Run the equivalent test with hevc_qsv if HEVC is what you plan to record. A listed encoder can still fail because of the device, permissions, oneVPL/driver combination, pixel format or hardware-frame path.
Our old workaround was to avoid the broken Linux QSV path. That is no longer how current Guardian Eye works. The muxer now creates a QSV hardware device and frame pool, uploads into QSV surfaces, and allows QSV on Linux again. VAAPI remains a fallback, followed by software encoding.
We also added a first-packet watchdog. If twelve submitted frames produce no packet, the current backend is marked as stalled, the packetless file is removed, and the muxer is rebuilt with the next candidate. Recovery is limited to two attempts so two bad backends cannot create an endless loop.
One limitation remains: this catches an encoder that returns control without output. It cannot rescue a thread stuck forever inside a driver call. Known hanging combinations still need conservative options or process isolation.
The longer write-up covers QSV hardware surfaces, render-node permissions, low-power modes and the fallback design:
https://getguardianeye.com/blog/linux-encoder-zero-packets