diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-15 16:00:06 -0500 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-15 16:00:06 -0500 |
| commit | 43bbf490dca972cb96fcf1d69dd0b6859377e452 (patch) | |
| tree | ac836f0dfcd50b3302d29a88402b1753127f7cf2 /tools | |
| parent | f37f1f6a92939a8c147da1ed5a9032d9e8604ddc (diff) | |
| download | open-iov-43bbf490dca972cb96fcf1d69dd0b6859377e452.tar.gz open-iov-43bbf490dca972cb96fcf1d69dd0b6859377e452.zip | |
tools/guest-diag: tag BGRA capture records as GZF2 fmt 33
sdl_cap.c wrote the same 32 in the GZF2 record format field for every
capture path, so gzf2_video.py's auto mode decoded ARGB8888/X-surface
frames (BGRA byte order on little-endian) as RGBA and swapped red and
blue. Make the field the documented pixel-order tag: 32 = RGBA8 from
the GL swap path (glReadPixels GL_RGBA), 33 = BGRA8 from
SDL_RenderReadPixels(ARGB8888) and X window surfaces; xgrab.c tags its
ZPixmap BGRA records 33 as well.
gzf2_video.py continues to accept both tags (and old all-32 captures
via --pix-fmt bgra), checks ffmpeg/ffprobe before indexing, and keeps
the final resampled endpoint so a 90-frame 60 fps capture encodes as
90 frames instead of 88.
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/guest-diag/gzf2_video.py | 25 | ||||
| -rw-r--r-- | tools/guest-diag/sdl_cap.c | 21 | ||||
| -rw-r--r-- | tools/guest-diag/xgrab.c | 5 |
3 files changed, 36 insertions, 15 deletions
diff --git a/tools/guest-diag/gzf2_video.py b/tools/guest-diag/gzf2_video.py index aa88aa0..8635ca0 100644 --- a/tools/guest-diag/gzf2_video.py +++ b/tools/guest-diag/gzf2_video.py @@ -9,9 +9,11 @@ # # Format: [0..8) = "GZCAPV01", zero pad to 4096; then records: # 4B "GZF2" | u32 w | u32 h | u32 stride | u32 fmt | u64 ts_ns | u64 seq | -# stride*h bytes, top-down. fmt 32 = RGBA8 (GL swap path), 33 = BGRA8 -# (SDL_RenderReadPixels ARGB8888 / X surface). A trailing -# "GZF2" + u64 0xffffffffffffffff marks a clean stop. +# stride*h bytes, top-down. fmt is a pixel-order tag: 32 = RGBA8 (GL swap +# path, glReadPixels GL_RGBA), 33 = BGRA8 (SDL_RenderReadPixels ARGB8888 / +# X surface). A trailing "GZF2" + u64 0xffffffffffffffff marks a clean stop. +# Captures written before the 33 tag existed recorded BGRA paths as 32; +# decode those with --pix-fmt bgra. # # Usage: # gzf2_video.py CAPTURE OUT.mp4 [--fps 60] [--skip 5] [--duration 20] @@ -22,6 +24,7 @@ import bisect import hashlib import json import os +import shutil import struct import subprocess import sys @@ -89,7 +92,13 @@ def pick_frames(recs, fps, skip_s, duration_s): cand = [r for r in recs if r["ts"] >= t0] or recs ts = [r["ts"] for r in cand] span = (ts[-1] - ts[0]) / 1e9 - n = int(duration_s * fps) if duration_s else max(1, int(span * fps)) + if duration_s: + n = max(1, int(round(duration_s * fps))) + else: + # Nearest-frame sampling must include both endpoints: a capture that + # spans k frame periods yields k+1 output frames (int() alone drops + # the last frame and can lose one more to float formatting). + n = max(1, int(round(span * fps)) + 1) picks, held = [], 0 for i in range(n): want = ts[0] + int(i * 1e9 / fps) @@ -179,10 +188,16 @@ def main(): default="libx264") ap.add_argument("--crf", type=int, default=18) ap.add_argument("--pix-fmt", choices=["auto", "rgba", "bgra"], - default="auto", help="auto uses record fmt (33=BGRA)") + default="auto", + help="auto uses the record tag (32=RGBA, 33=BGRA); " + "old BGRA captures tagged 32 need bgra") ap.add_argument("--info-only", action="store_true") args = ap.parse_args() + for tool in ("ffmpeg", "ffprobe"): + if not shutil.which(tool): + die(f"{tool} not found in PATH") + recs, clean, corrupt = index_capture(args.capture) uniq = len({r["md5"] for r in recs}) span = (recs[-1]["ts"] - recs[0]["ts"]) / 1e9 diff --git a/tools/guest-diag/sdl_cap.c b/tools/guest-diag/sdl_cap.c index 5b9c096..f875ed0 100644 --- a/tools/guest-diag/sdl_cap.c +++ b/tools/guest-diag/sdl_cap.c @@ -11,9 +11,12 @@ // File layout (little endian): // [0] 8B magic "GZCAPV01" (+ 4088 B reserved zero) // [4096] frame records: -// 4B "GZF2" | u32 w | u32 h | u32 stride | u32 bpp +// 4B "GZF2" | u32 w | u32 h | u32 stride | u32 fmt // u64 ts_ns (CLOCK_MONOTONIC) | u64 seq | stride*h bytes (top-down) -// A trailing record with seq = 0xFFFFFFFFFFFFFFFF marks clean stop. +// fmt is a pixel-order tag (not bits per pixel): 32 = RGBA8 from the GL +// swap path (glReadPixels GL_RGBA), 33 = BGRA8 from SDL_RenderReadPixels +// ARGB8888 or an X window surface. A trailing record with +// seq = 0xFFFFFFFFFFFFFFFF marks clean stop. // // Env: GZCAP (path, default /dev/shm/gzframes.bin), GZCAP_MAXF (0=unlimited) #define _GNU_SOURCE @@ -26,6 +29,8 @@ #include <unistd.h> #define ARGB8888 0x16362004u +#define GZF2_FMT_RGBA 32 +#define GZF2_FMT_BGRA 33 typedef struct SDL_Renderer SDL_Renderer; typedef struct SDL_Window SDL_Window; @@ -87,7 +92,7 @@ __attribute__((constructor)) static void cap_init(void) if (h) real_glReadPixels = dlsym(h, "glReadPixels"); } - fprintf(stderr, "[gzcap] init pid=%d path=%s present=%p updwin=%p swap=%p read=%p gros=%p\n", + fprintf(stderr, "[gzcap] init pid=%d path=%s present=%p updwin=%p swap=%p read=%p gros=%p readpix=%p\n", (int)getpid(), p, (void *)real_RenderPresent, (void *)real_UpdateWindowSurface, (void *)real_GL_SwapWindow, (void *)real_RenderReadPixels, (void *)real_GetRendererOutputSize, (void *)real_glReadPixels); @@ -102,7 +107,7 @@ __attribute__((constructor)) static void cap_init(void) fflush(cap_fp); } -static void cap_record(const void *px, int w, int h, int stride, int bpp) +static void cap_record(const void *px, int w, int h, int stride, int fmt) { struct timespec ts; uint64_t tns, seq; @@ -126,7 +131,7 @@ static void cap_record(const void *px, int w, int h, int stride, int bpp) rh[0] = (uint32_t)w; rh[1] = (uint32_t)h; rh[2] = (uint32_t)stride; - rh[3] = (uint32_t)bpp; + rh[3] = (uint32_t)fmt; fwrite(m, 1, 4, cap_fp); fwrite(rh, 4, 4, cap_fp); fwrite(&tns, 8, 1, cap_fp); @@ -161,7 +166,7 @@ int SDL_RenderPresent(SDL_Renderer *r) } if (buf && real_RenderReadPixels(r, NULL, ARGB8888, buf, w * 4) == 0) - cap_record(buf, w, h, w * 4, 32); + cap_record(buf, w, h, w * 4, GZF2_FMT_BGRA); } } if (real_RenderPresent) @@ -180,7 +185,7 @@ int SDL_UpdateWindowSurface(SDL_Window *win) if (s && s->pixels && s->w > 0 && s->h > 0 && s->pitch > 0 && s->w <= 4096 && s->h <= 4096) - cap_record(s->pixels, s->w, s->h, s->pitch, 32); + cap_record(s->pixels, s->w, s->h, s->pitch, GZF2_FMT_BGRA); } if (real_UpdateWindowSurface) return real_UpdateWindowSurface(win); @@ -221,7 +226,7 @@ void SDL_GL_SwapWindow(SDL_Window *win) memcpy(tmp + (size_t)y * w * 4, gbuf + (size_t)(h - 1 - y) * w * 4, (size_t)w * 4); - cap_record(tmp, w, h, w * 4, 32); + cap_record(tmp, w, h, w * 4, GZF2_FMT_RGBA); free(tmp); } } diff --git a/tools/guest-diag/xgrab.c b/tools/guest-diag/xgrab.c index eb605d6..bca23c0 100644 --- a/tools/guest-diag/xgrab.c +++ b/tools/guest-diag/xgrab.c @@ -3,8 +3,9 @@ // Pure client-side XGetImage on a software X server (Xvfb): no GL, no driver, // no present-path hooks. Same record format as sdl_cap.c: // [0] 8B "GZCAPV01" + zero pad to 4096 -// [4096..] records: 4B "GZF2" | u32 w | u32 h | u32 stride | u32 bpp | +// [4096..] records: 4B "GZF2" | u32 w | u32 h | u32 stride | u32 fmt | // u64 ts_ns | u64 seq | stride*h bytes (top-down BGRA) +// fmt is a pixel-order tag: 33 = BGRA8 (ZPixmap on little-endian X). // Trailing record seq=0xFFFFFFFFFFFFFFFF on clean stop. // Usage: xgrab <display> <out> <maxframes|0> <pace_us|0> #include <X11/Xlib.h> @@ -83,7 +84,7 @@ int main(int argc, char **argv) rh[0] = (uint32_t)r_w; rh[1] = (uint32_t)r_h; rh[2] = (uint32_t)r_w * 4; - rh[3] = 32; + rh[3] = 33; /* GZF2 pixel-order tag: BGRA8 */ fwrite(m, 1, 4, f); fwrite(rh, 4, 4, f); fwrite(&tns, 8, 1, f); |
