Live view hardware encoder: fixes from the first device run

- fp_venc: accept the source's buffer count (the Frame's v4l2loopback has max_buffers=2; asking for 3 failed the
  probe); OUTPUT buffers remember which picture they hold, so repeats (standby, still scenes) skip the conversion
  (720p: 35% -> 2.5% of a core with the headset asleep).
- Pipeline: raw H.264 stamped on arrival (-use_wallclock_as_timestamps, same clock as pulse) with a 32-byte probe:
  -framerate was ignored, -fflags nobuffer lost the first seconds, frames numbered from 0 held all output ~7 s next
  to the sound. frag_keyframe so a requested keyframe starts a fragment (a joining viewer starts there).

Verified on the Frame (headset asleep): selftest ok (104 cases), probe at 360p-1080p, end to end through
livestream.start: 720p36 + AAC in 0.1 s fragments, audio and video spans match, a second viewer starts on its
requested keyframe at once, nothing left running after stop; FP_VENC_DISABLE=1 falls back to x264.
This commit is contained in:
spoopyghosty0 committed 2026-10-07 17:50:57 -04:00
1 parent 0ae754fa5d
commit da7655ee25
6 files changed
+47 -15

No files matched your search

+1 -1
View File
@@ -12,6 +12,6 @@ c10412dd76a65a1d587b7ed226367d1a6f2bec238c733c97ee67eb8870c868e8 ./armeabi-v7a/
1871eae093432d277da4bc751bf5f3269dfcf11f9168260b0c3caadb0dedb19d ./dex/oculusos-stubs.dex
1aa733117cf57ccff7cf23f0425dbfd73b0332a3ef1c905ceeca0ccd0449c4e0 ./linux-arm64/XR_APILAYER_FRAMEPORT_timefix.json
28c2430a02bbd8902c5bfb9562c6fd0e318654f9e05c095bfeb94b0450ab1b07 ./linux-arm64/libxr_frameport_timefix.so
33a4060010a9d2c5d1515fa2d5294beb664edeaef7d37f4d325ab6e6ce6a5f0e ./linux-arm64-bin/fp_venc
a4a6d9f41c290a421842294a39562e1e6f0ca271bb47b72f615c808560338f6a ./linux-arm64-bin/fp_venc
c2c70f4af1a5c3e6089dff1130161dd417f8044f99ec639eeecf1083930aab16 ./win-x64/fp_oculushmd.exe
60ebc5fea05b8945082e2ca8b9f2eb74616012a26eb50133af273dff0fcddc42 ./win-x64/fp_vrsettings.exe
Binary file not shown.
+14
View File
@@ -160,6 +160,20 @@
- The panel's current refresh rate can be read without privileges through DRM: `/dev/dri/card0` is mode 0666, and
GETCRTC reports e.g. `2*2160x2160_96` (clock 1402720 kHz / 4448 × 3285 = 96 Hz) even while the headset sleeps.
The panel offers 72/80/90/96/108/120/144 Hz.
- `/dev/video99` (v4l2loopback) has `max_buffers=2`: a reader asking for more gets 2.
- Mid-stream keyframe requests (FORCE_KEY_FRAME) take effect on the next frame, and the GOP restarts from there.
- Measured 2026-10-07 with the headset asleep (still picture):
- `fp_venc` alone at 32/36 fps: 1% of a core at 1080p, 2.5% at 720p.
- Live view end to end: `fp_venc` 2.6% + ffmpeg 8.4% (AAC encoding + muxing).
- Conversion cost per new picture (self-test, NEON): 1.5 ms at 1080p→1080p. Box-scaling to 720p takes 5.6 ms
(~11 ms at idle clocks), so with live content 1080p costs the CPU *less* than the scaled qualities.
- ffmpeg with raw H.264 on a pipe:
- `-framerate` is ignored.
- `-fflags nobuffer` loses the first seconds of tiny frames.
- Numbering frames from 0 (`setts`) next to pulse's wall-clock audio holds all output ~7 s.
- What works: `-probesize 32 -analyzeduration 0 -use_wallclock_as_timestamps 1`, plus `frag_keyframe` so that a
requested keyframe starts a fragment.
- The default sink is SUSPENDED while nothing plays; its monitor still delivers (silent) audio.
## Text input
+18 -8
View File
@@ -350,6 +350,8 @@ struct enc {
uint8_t *out_mem[ENC_BUFS], *cap_mem[ENC_BUFS];
uint32_t out_len[ENC_BUFS], cap_len[ENC_BUFS];
int out_free[ENC_BUFS];
long out_pic[ENC_BUFS]; /* which picture each OUTPUT buffer holds (0 = black, -1 = none): a repeat that finds
* its picture already in the buffer skips the conversion (standby, still scenes) */
};
static int src_open(struct src *s, const char *path)
@@ -375,8 +377,10 @@ static int src_open(struct src *s, const char *path)
memset(&rb, 0, sizeof rb);
rb.count = SRC_BUFS, rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, rb.memory = V4L2_MEMORY_MMAP;
r = sys_ioctl(s->fd, VIDIOC_REQBUFS, &rb);
if (r < 0 || rb.count < SRC_BUFS) return fail(3, "source VIDIOC_REQBUFS", r < 0 ? r : -ENOMEM);
for (int i = 0; i < SRC_BUFS; i++) {
/* The driver may grant fewer (the Frame's v4l2loopback has max_buffers=2); two are enough: one held, one filling. */
if (r < 0 || rb.count < 2) return fail(3, "source VIDIOC_REQBUFS", r < 0 ? r : -ENOMEM);
if (rb.count > SRC_BUFS) rb.count = SRC_BUFS;
for (int i = 0; i < (int)rb.count; i++) {
struct v4l2_buffer b;
memset(&b, 0, sizeof b);
b.index = (uint32_t)i, b.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, b.memory = V4L2_MEMORY_MMAP;
@@ -710,6 +714,8 @@ int main(int argc, char **argv, char **envp)
const int64_t t0 = now_ns(), period = 1000000000 / fps;
int64_t next_stats = t0 + 10000000000LL, quiet_src = 0, quiet_enc = 0;
long slot = 0; /* next slot to submit; due at t0 + slot * 1e9 / fps */
long pic = 0; /* current picture: 0 = black (no source frame yet), +1 per new source frame */
for (int i = 0; i < ENC_BUFS; i++) e.out_pic[i] = -1;
int want_key = 0, stdin_open = 1, sent_any = 0, rc = 0;
for (;;) {
@@ -726,12 +732,16 @@ int main(int argc, char **argv, char **envp)
while (i < ENC_BUFS && !e.out_free[i]) i++;
if (i == ENC_BUFS) break;
uint8_t *m = e.out_mem[i];
if (s.held >= 0) {
fp_convert(s.mem[s.held], s.w, s.h, s.stride, m, e.stride, m + (size_t)e.stride * e.hpad, e.stride,
ow, oh, scratch);
} else { /* no source frame yet: black */
memset(m, 16, (size_t)e.stride * oh);
memset(m + (size_t)e.stride * e.hpad, 128, (size_t)e.stride * oh / 2);
if (s.held >= 0 && s.fresh) pic++;
if (e.out_pic[i] != pic) {
if (s.held >= 0) {
fp_convert(s.mem[s.held], s.w, s.h, s.stride, m, e.stride, m + (size_t)e.stride * e.hpad,
e.stride, ow, oh, scratch);
} else { /* no source frame yet: black */
memset(m, 16, (size_t)e.stride * oh);
memset(m + (size_t)e.stride * e.hpad, 128, (size_t)e.stride * oh / 2);
}
e.out_pic[i] = pic;
}
if (!s.fresh && sent_any) st.repeats++;
s.fresh = 0;
+8 -4
View File
@@ -428,11 +428,15 @@ info=
hwfps=$(printf %s "$info" | sed -n 's/.*"fps":\\([0-9][0-9]*\\).*/\\1/p')
if [ -n "$hwfps" ]; then
echo "{INFO_PREFIX}encoder=hardware fps=$hwfps" >&2
# fp_venc reads the SSH channel (stdin): "k" = keyframe, EOF = stop; ffmpeg then ends with it (-shortest)
# fp_venc reads the SSH channel (stdin): "k" = keyframe, EOF = stop; ffmpeg then ends with it (-shortest).
# Raw H.264 has no timestamps: frames are stamped on arrival (wall clock, like pulse's audio; fp_venc sends them on
# its even fps grid, so arrival = capture + a constant). Measured on the Frame (2026-10-07): -framerate is ignored;
# numbering frames from 0 (setts) held all output ~7 s when sound was on (interleaving against pulse's wall clock);
# -fflags nobuffer lost the first seconds of a still picture's tiny frames; a big probesize waits seconds for one.
# frag_keyframe: a requested keyframe starts its own fragment, where a joining viewer can begin.
"$venc" {venc_args} --fps "$hwfps" | nice -n 10 ffmpeg -nostdin -hide_banner -loglevel error \\
-probesize 262144 -analyzeduration 500000 -fflags +genpts+nobuffer -f h264 -framerate "$hwfps" -i pipe:0 \\
"${{audio[@]}}" -c:v copy -shortest \\
-f mp4 -movflags empty_moov+default_base_moof -frag_duration 100000 -
-probesize 32 -analyzeduration 0 -use_wallclock_as_timestamps 1 -f h264 -i pipe:0 "${{audio[@]}}" \\
-c:v copy -shortest -f mp4 -movflags empty_moov+default_base_moof+frag_keyframe -frag_duration 100000 -
exit $?
fi
echo "{INFO_PREFIX}encoder=software fps={fps}" >&2
+6 -2
View File
@@ -243,7 +243,9 @@ def test_relay_with_audio():
def test_source_command_captures_default_output():
cmd = L.source_command()
assert "pactl get-default-sink" in cmd and "$sink.monitor" in cmd and "-c:a aac" in cmd
assert "-ts mono2abs" in cmd and "-use_wallclock_as_timestamps" not in cmd # both on pulse's wall clock
audio = cmd[cmd.index("audio=(-thread_queue_size"):cmd.index("fi\n", cmd.index("audio=(-thread_queue_size"))]
assert "-ts mono2abs" in cmd and "-use_wallclock_as_timestamps" not in audio # both on pulse's wall clock
# (the hardware path stamps its raw H.264 input with the wall clock; the pulse input never gets that flag)
assert "aresample=async=1" in cmd
@@ -291,7 +293,9 @@ def test_source_command_hardware_path(tmp_path):
assert p.wait(timeout=5) == 0
assert b"live: encoder=hardware fps=32" in p.stderr.read()
args = (tmp_path / "ffmpeg.args").read_text()
assert "-f h264 -framerate 32 -i pipe:0" in args and "-c:v copy" in args and "libx264" not in args
assert "-f h264 -i pipe:0" in args and "-c:v copy" in args and "libx264" not in args
assert "-use_wallclock_as_timestamps 1" in args and "nobuffer" not in args # same clock as pulse's audio
assert "+frag_keyframe" in args # a requested keyframe starts a fragment (where a new viewer begins)
venc_calls = (tmp_path / "venc.args").read_text().splitlines()
assert venc_calls[0].startswith("--probe ") and "--height 720" in venc_calls[0] and "--bitrate 3000000" in \
venc_calls[0]