← Commit history

docs/recording: how a take spends its bits (recording.md)

John Lauer ·e1c636c001 ·1mo ago ·parent b48f93d
1 file changed +255−133
docs/recording.md+255−133
@@ -1,133 +1,255 @@-# Screen recording in Adom Bridge (H.264 / H.265)--Adom Bridge records your screen, a whole monitor or a single window, to an-**MP4** file, on your machine, with the system audio mixed in. It is native:-Windows Graphics Capture for the pixels and Media Foundation for the encode,-with a **hardware encoder** (NVENC / Quick Sync / AMD) doing the work and the-frames staying on the GPU end to end, for both codecs. No browser, no screenshot-loop, no extra capture app.--## It can never record you silently--While a take is running, a **4 pixel teal dot** sits in the corner of your-screen, out of the way of whatever you are recording. It is deliberately tiny-and deliberately still: recordings often get sped up 10x afterwards, and-anything that moves looks wrong at 10x. Starting a desktop recording also forces-the AI to confirm it means the *whole desktop* and not just one window. The clip-is a normal MP4 you keep; Adom Bridge prunes its own recordings folder (oldest-first) so it never fills your disk, and never touches a clip that is still being-written.--If you want the Adom orbital on screen for its own sake, that is a separate-verb: `desktop_orbital {"action":"show","size":220,"corner":"top-left"}` puts-the real Orbital Lab animation up as a transparent, click-through overlay, the-same single-file WebGL loader Hydrogen shows while it installs. It has nothing-to do with recording.--## The verbs--| Verb | What it does |-|---|---|-| `desktop_record_start` | Start a take. `{monitor}` (or `"primary"`) for a screen; `{hwnd}` / `{titleContains}` for one window. Optional `fps` (default 30), `codec` (`h264` default, `h265`), `bitrate` (bits/s), `audio` (default on for a monitor). |-| `desktop_record_stop` | Stop and finalise. Answers within its budget; if the encoder is still closing the file it reports `state:"finalising"` and you poll status, so the take is never dropped. |-| `desktop_record_status` | Live state: `capturedFrames`, `captureFps`, `bytes`, `droppedFrames`, `framePath`, `state`. |-| `desktop_record_list` | Finished clips on disk. |-| `desktop_record_encoders` | Which codecs this machine can hardware-encode. |-| `desktop_orbital` | The Orbital Lab overlay, unrelated to recording. |--**`captureFps` is the honest number.** Windows Graphics Capture only hands over-a frame when the desktop actually changed, so the capture source, not the-encoder, sets the real framerate. Ask for `fps: 60` and you get it if the screen-is doing something; ask for 30 and 30 is your ceiling no matter how fast the-machine is.--## H.264 vs H.265: see it and size it--Ask the machine what it has:--```-desktop_record_encoders--> { codecs: {-      h264: { available:true, hardware:true, encoders:[{name:"NVIDIA H.264 Encoder MFT", hardware:true}, ...] },-      h265: { available:true, hardware:true, encoders:[{name:"NVIDIA HEVC Encoder MFT",  hardware:true}, ...] } },-    default:"h264" }-```--- **H.264** is the default, encodes at **High** profile, and plays everywhere.-- **H.265 (HEVC)** reaches the same picture for fewer bits. Halve the `bitrate`-  against H.264 and you get a file about half the size that still holds up. It-  needs a reasonably modern player.--### Four takes of the same scene--Adom Bridge recorded the **Orbital Lab** (a live Babylon.js 3D hydrogen-orbital-animation, spinning the whole time) in a browser window sized to exactly 80% of-the screen, capturing the **full screen** on an idle test PC with an NVIDIA GPU.-Same scene, 20 seconds, at 60 fps, at two resolutions and in both codecs.--Both codecs were given the **same bitrate** at each resolution. That is the fair-comparison: it shows what each one does with an identical budget, rather than-flattering one of them. Every number is from `ffprobe` on the finished file, and-**no take dropped a single frame**.--| Clip | Resolution | Codec | Bitrate asked for | File size | Bitrate measured | Framerate |-|---|---|---|---|---|---|---|-| 1 | 1080p | H.264 High | 8 Mbps | 18.6 MB | 7.44 Mbps | 55.1 fps |-| 2 | 1080p | **H.265 Main** | 8 Mbps | **17.4 MB** | 6.97 Mbps | 55.2 fps |-| 3 | 1440p | H.264 High | 14 Mbps | 32.5 MB | 12.98 Mbps | 54.9 fps |-| 4 | 1440p | **H.265 Main** | 14 Mbps | **30.4 MB** | 12.15 Mbps | 55.0 fps |--Given the same budget, H.265 comes in a little smaller and spends its bits more-efficiently on the picture. The bigger win is the one this table does not show:-ask H.265 for half the bitrate and you get roughly half the file for a-comparable image. Both codecs ran on the GPU zero-copy path here, and both-captured about 55 real frames per second.--#### Clip 1 of 4 - 1080p, H.264 High - 18.6 MB at 7.44 Mbps, 55.1 fps--<video src="docs/recording/orbital-1080p-h264.mp4" controls muted loop playsinline width="720"></video>--#### Clip 2 of 4 - 1080p, H.265 Main - 17.4 MB at 6.97 Mbps, 55.2 fps--<video src="docs/recording/orbital-1080p-h265.mp4" controls muted loop playsinline width="720"></video>--#### Clip 3 of 4 - 1440p, H.264 High - 32.5 MB at 12.98 Mbps, 54.9 fps--<video src="docs/recording/orbital-1440p-h264.mp4" controls muted loop playsinline width="720"></video>--#### Clip 4 of 4 - 1440p, H.265 Main - 30.4 MB at 12.15 Mbps, 55.0 fps--<video src="docs/recording/orbital-1440p-h265.mp4" controls muted loop playsinline width="720"></video>--If your Markdown viewer does not play inline video, the four files are on this-page under `docs/recording/`.--## Examples--```bash-# Record the primary monitor with system audio, H.264 (default)-desktop_record_start '{"monitor":"primary","audio":true,"reason":"capturing the export walkthrough"}'-desktop_record_stop  '{}'--# 60 fps, H.265 at half the bitrate for roughly half the file-desktop_record_start '{"monitor":"primary","codec":"h265","fps":60,"bitrate":7000000,"reason":"..."}'--# Record one window in the background, no audio-desktop_record_start '{"titleContains":"KiCad","codec":"h265","bitrate":6000000,"reason":"..."}'-```--## Under the hood--- **GPU zero-copy, both codecs:** the captured frame goes WGC surface, then a-  pooled GPU texture, then the hardware encoder through a shared Direct3D-  device. No copy to system memory. `desktop_record_status` reports `framePath`-  so you can see which path a take actually took.-- **H.265 path:** the hardware HEVC encoder wants NV12, so Adom Bridge runs a-  Direct3D VideoProcessor to convert each frame on the GPU, and writes HEVC into-  a standard MP4 (`hvc1`) alongside an AAC audio track.-- **Full colour range** is preserved end to end, so smooth shading on a dark-  scene does not band.-- **Timestamps track a wall clock shared with the audio**, so a long screen and-  audio recording stays in sync and finalises quickly instead of stalling. A-  tick with no new frame behind it is skipped rather than re-encoded, so the-  framerate in the file is the framerate that was captured.+# Screen recording in Adom Bridge (H.264 / H.265)
+
+Adom Bridge records your screen, a whole monitor or a single window, to an
+**MP4** file, on your machine, with the system audio mixed in. It is native:
+Windows Graphics Capture for the pixels and Media Foundation for the encode,
+with a **hardware encoder** (NVENC / Quick Sync / AMD) doing the work and the
+frames staying on the GPU end to end, for both codecs. No browser, no screenshot
+loop, no extra capture app.
+
+## It can never record you silently
+
+While a take is running, a **4 pixel teal dot** sits in the corner of your
+screen, out of the way of whatever you are recording. It is deliberately tiny
+and deliberately still: recordings often get sped up 10x afterwards, and
+anything that moves looks wrong at 10x. Starting a desktop recording also forces
+the AI to confirm it means the *whole desktop* and not just one window. The clip
+is a normal MP4 you keep; Adom Bridge prunes its own recordings folder (oldest
+first) so it never fills your disk, and never touches a clip that is still being
+written.
+
+If you want the Adom orbital on screen for its own sake, that is a separate
+verb: `desktop_orbital {"action":"show","size":220,"corner":"top-left"}` puts
+the real Orbital Lab animation up as a transparent, click-through overlay, the
+same single-file WebGL loader Hydrogen shows while it installs. It has nothing
+to do with recording.
+
+## How a take spends its bits (and why the defaults are what they are)
+
+Most AIs will never pick an encoder option; they call `desktop_record_start`
+with a monitor and a reason and take what they get. So the defaults are the
+product, and they were chosen against two opposite jobs the same recorder has
+to do well:
+
+1. **A long take where almost nothing happens.** Someone records a 40-minute
+   session and the screen is a document or a terminal for most of it. The file
+   must stay small and the video must still be the right length.
+2. **A burst of fast motion.** A video plays, a page scrolls, a 3D model spins.
+   The picture must stay sharp; a smeared, blocky recording of the one moment
+   that mattered is worse than no recording.
+
+### Where the frames come from
+
+Windows Graphics Capture does not deliver 30 frames a second. It delivers a
+frame **when the screen changes**, and nothing at all while it does not. A
+still screen therefore yields a handful of frames per minute, and a fast scene
+yields the full rate you asked for (30 by default, up to 60). Adom Bridge
+records exactly what arrives: it never pads a still screen with duplicate
+frames (one held frame every 2 seconds is re-written so any player can seek
+into a long pause, and that is all).
+
+That alone keeps still takes tiny. It also used to make them *short*: the MP4
+writer inside Media Foundation gives the last frame of every 2-second fragment
+a fixed length whatever the real gap was, so a 22-second still take played as
+18 seconds. Since 2.1.125 the recorder stamps every frame on the take's wall
+clock and, after the file is closed, rewrites the container's frame timings
+from those stamps. The encoded length now matches the wall clock to within a
+few milliseconds for still, moving and mixed content alike; the stop and status
+replies report it as `timeline: {repaired, fragments, samples, endBeforeMs,
+endAfterMs}`.
+
+### What the encoder is allowed to spend
+
+The other half is rate control, and the default changed in 2.1.128. The old
+default was a fixed bit-rate cap of about a tenth of a bit per pixel per frame
+(11 Mbit/s at 1440p). A still screen never came near it, so it looked free; a
+fast scene slammed into it and the encoder smeared. Three modes exist now:
+
+| Mode | How you get it | What it does | When to use it |
+|---|---|---|---|
+| **VBR** (default) | say nothing | Peak-constrained variable bit rate: a generous **mean** (3x the old cap, about 33 Mbit/s at 1440p30, 72 at 4K) and a **peak** of twice that. The encoder spends almost nothing on a still screen and up to the peak on motion. | Every ordinary take. It is what you get when you do not think about it, and it is tuned so you do not have to. |
+| **CBR** | `bitrate` in bits/s | Constant bit rate at exactly that number. | A hard cap: a slow disk, a known upload budget, a file that must not exceed a size. Expect fast motion to soften at low rates. |
+| **Constant quality** | `quality` 1..100 | The encoder holds a picture quality and spends whatever it needs. | Only on content you know. The NVIDIA encoder honours **no cap** in this mode: a noise field at quality 75 wrote 185 Mbit/s with a 20 Mbit/s cap set. Opt-in for that reason. |
+
+`maxBitrate` (bits/s) moves the VBR peak; `codec:"h265"` halves the file for the
+same picture where a modern player is fine (next section). The start reply and
+`desktop_record_status` carry `rateControl` (`vbr` | `cbr` | `quality`) so you
+can see which mode a take actually ran in, and `desktop_disk_free` tells you
+how much room the recordings drive has before you start a long one.
+
+### Five takes that show it
+
+All recorded by the released 2.1.128 on an idle 1440p test PC (NVIDIA H.264
+encoder, 30 fps, no audio, full screen, synthetic pages). Every number is from
+`ffprobe` on the finished file; `wall` is the take's own clock, `encoded` is
+the file's duration. Watch the still one to the end: it is 7 frames and plays
+for 9.5 seconds.
+
+| Take | What is on screen | Mode | Wall | Encoded | Frames | File | Bit rate |
+|---|---|---|---|---|---|---|---|
+| 1 | a still page, nothing changes | VBR (default) | 9.51 s | 9.51 s | 7 | **0.20 MB** | 0.17 Mbit/s |
+| 2 | a full-screen canvas animation (40 bouncing discs, moving text) | VBR (default) | 9.44 s | 9.45 s | 265 | 26.4 MB | 22.3 Mbit/s |
+| 3 | worst case: dense scrolling text over a moving noise field | VBR (default) | 9.48 s | 9.49 s | 265 | 36.2 MB | 30.6 Mbit/s |
+| 4 | the same worst case | CBR 11 Mbit/s (the old default) | 9.50 s | 9.50 s | 266 | 16.5 MB | 13.8 Mbit/s |
+| 5 | still page 4 s, then the animation 7 s, then still again | VBR (default) | 15.63 s | 15.63 s | 200 | 23.7 MB | 12.1 Mbit/s |
+
+Take 1 is the long-recording case: the file costs nothing while the screen is
+still, and the length is right. Take 2 is ordinary motion, where the default
+spends about twice what the old cap allowed and the discs keep clean edges.
+Takes 3 and 4 are the same 9 seconds of the nastiest content a screen can show,
+recorded with the new default and with the old one; the crop below is the same
+frame from each. Take 5 is what real sessions look like: the frame count and
+the bit rate follow the motion, and nothing is lost in the still parts.
+
+![Old default vs new default on the stress page, same frame](docs/recording/stress-crop-cbr-vs-vbr.png)
+
+#### Take 1 - still page, 7 frames, 0.20 MB, plays 9.5 s
+
+<video src="docs/recording/still-default.mp4" controls muted loop playsinline width="720"></video>
+
+#### Take 2 - canvas animation, default VBR, 26.4 MB at 22.3 Mbit/s
+
+<video src="docs/recording/moving-default.mp4" controls muted loop playsinline width="720"></video>
+
+#### Take 3 - stress page, default VBR, 36.2 MB at 30.6 Mbit/s
+
+<video src="docs/recording/stress-default.mp4" controls muted loop playsinline width="720"></video>
+
+#### Take 4 - stress page, CBR 11 Mbit/s (the old default), 16.5 MB
+
+<video src="docs/recording/stress-cbr11.mp4" controls muted loop playsinline width="720"></video>
+
+#### Take 5 - still, then motion, then still; default VBR, 23.7 MB
+
+<video src="docs/recording/mixed-default.mp4" controls muted loop playsinline width="720"></video>
+
+### If you do want to choose
+
+```bash
+# The default: say nothing. VBR, exact length, small when still, sharp when moving.
+desktop_record_start '{"monitor":"primary","reason":"recording the export walkthrough"}'
+
+# A hard cap for a slow disk or a size limit (constant bit rate, 8 Mbit/s)
+desktop_record_start '{"monitor":"primary","bitrate":8000000,"reason":"..."}'
+
+# Raise the VBR peak for a 4K box that plays video (bits/s)
+desktop_record_start '{"monitor":"primary","maxBitrate":200000000,"reason":"..."}'
+
+# Constant quality, opt-in, only on content you know will not be a video or noise
+desktop_record_start '{"titleContains":"KiCad","quality":80,"reason":"..."}'
+
+# Same picture, about half the file, needs a modern player
+desktop_record_start '{"monitor":"primary","codec":"h265","reason":"..."}'
+```
+
+## The verbs
+
+| Verb | What it does |
+|---|---|
+| `desktop_record_start` | Start a take. `{monitor}` (or `"primary"`) for a screen; `{hwnd}` / `{titleContains}` for one window. Optional `fps` (default 30), `codec` (`h264` default, `h265`), `bitrate` (bits/s, switches to CBR), `maxBitrate` (the VBR peak), `quality` (1..100, constant quality), `audio` (default on for a monitor). |
+| `desktop_record_stop` | Stop and finalise. Answers within its budget; if the encoder is still closing the file it reports `state:"finalising"` and you poll status, so the take is never dropped. |
+| `desktop_record_status` | Live state: `capturedFrames`, `captureFps`, `bytes`, `droppedFrames`, `framePath`, `state`. |
+| `desktop_record_list` | Finished clips on disk. |
+| `desktop_record_encoders` | Which codecs this machine can hardware-encode. |
+| `desktop_disk_free` | Free space on the recordings drive (or any path), ungated, so a long take can be refused before it fills the disk. |
+| `desktop_orbital` | The Orbital Lab overlay, unrelated to recording. |
+
+**`captureFps` is the honest number.** Windows Graphics Capture only hands over
+a frame when the desktop actually changed, so the capture source, not the
+encoder, sets the real framerate. Ask for `fps: 60` and you get it if the screen
+is doing something; ask for 30 and 30 is your ceiling no matter how fast the
+machine is.
+
+## H.264 vs H.265: see it and size it
+
+Ask the machine what it has:
+
+```
+desktop_record_encoders
+-> { codecs: {
+      h264: { available:true, hardware:true, encoders:[{name:"NVIDIA H.264 Encoder MFT", hardware:true}, ...] },
+      h265: { available:true, hardware:true, encoders:[{name:"NVIDIA HEVC Encoder MFT",  hardware:true}, ...] } },
+    default:"h264" }
+```
+
+- **H.264** is the default, encodes at **High** profile, and plays everywhere.
+- **H.265 (HEVC)** reaches the same picture for fewer bits. Halve the `bitrate`
+  against H.264 and you get a file about half the size that still holds up. It
+  needs a reasonably modern player.
+
+### Four takes of the same scene
+
+Adom Bridge recorded the **Orbital Lab** (a live Babylon.js 3D hydrogen-orbital
+animation, spinning the whole time) in a browser window sized to exactly 80% of
+the screen, capturing the **full screen** on an idle test PC with an NVIDIA GPU.
+Same scene, 20 seconds, at 60 fps, at two resolutions and in both codecs.
+
+Both codecs were given the **same bitrate** at each resolution. That is the fair
+comparison: it shows what each one does with an identical budget, rather than
+flattering one of them. Every number is from `ffprobe` on the finished file, and
+**no take dropped a single frame**.
+
+| Clip | Resolution | Codec | Bitrate asked for | File size | Bitrate measured | Framerate |
+|---|---|---|---|---|---|---|
+| 1 | 1080p | H.264 High | 8 Mbps | 18.6 MB | 7.44 Mbps | 55.1 fps |
+| 2 | 1080p | **H.265 Main** | 8 Mbps | **17.4 MB** | 6.97 Mbps | 55.2 fps |
+| 3 | 1440p | H.264 High | 14 Mbps | 32.5 MB | 12.98 Mbps | 54.9 fps |
+| 4 | 1440p | **H.265 Main** | 14 Mbps | **30.4 MB** | 12.15 Mbps | 55.0 fps |
+
+Given the same budget, H.265 comes in a little smaller and spends its bits more
+efficiently on the picture. The bigger win is the one this table does not show:
+ask H.265 for half the bitrate and you get roughly half the file for a
+comparable image. Both codecs ran on the GPU zero-copy path here, and both
+captured about 55 real frames per second.
+
+#### Clip 1 of 4 - 1080p, H.264 High - 18.6 MB at 7.44 Mbps, 55.1 fps
+
+<video src="docs/recording/orbital-1080p-h264.mp4" controls muted loop playsinline width="720"></video>
+
+#### Clip 2 of 4 - 1080p, H.265 Main - 17.4 MB at 6.97 Mbps, 55.2 fps
+
+<video src="docs/recording/orbital-1080p-h265.mp4" controls muted loop playsinline width="720"></video>
+
+#### Clip 3 of 4 - 1440p, H.264 High - 32.5 MB at 12.98 Mbps, 54.9 fps
+
+<video src="docs/recording/orbital-1440p-h264.mp4" controls muted loop playsinline width="720"></video>
+
+#### Clip 4 of 4 - 1440p, H.265 Main - 30.4 MB at 12.15 Mbps, 55.0 fps
+
+<video src="docs/recording/orbital-1440p-h265.mp4" controls muted loop playsinline width="720"></video>
+
+If your Markdown viewer does not play inline video, the four files are on this
+page under `docs/recording/`.
+
+## Examples
+
+```bash
+# Record the primary monitor with system audio, H.264 (default)
+desktop_record_start '{"monitor":"primary","audio":true,"reason":"capturing the export walkthrough"}'
+desktop_record_stop  '{}'
+
+# 60 fps, H.265 at half the bitrate for roughly half the file
+desktop_record_start '{"monitor":"primary","codec":"h265","fps":60,"bitrate":7000000,"reason":"..."}'
+
+# Record one window in the background, no audio
+desktop_record_start '{"titleContains":"KiCad","codec":"h265","bitrate":6000000,"reason":"..."}'
+```
+
+## Under the hood
+
+- **GPU zero-copy, both codecs:** the captured frame goes WGC surface, then a
+  pooled GPU texture, then the hardware encoder through a shared Direct3D
+  device. No copy to system memory. `desktop_record_status` reports `framePath`
+  so you can see which path a take actually took.
+- **H.265 path:** the hardware HEVC encoder wants NV12, so Adom Bridge runs a
+  Direct3D VideoProcessor to convert each frame on the GPU, and writes HEVC into
+  a standard MP4 (`hvc1`) alongside an AAC audio track.
+- **Full colour range** is preserved end to end, so smooth shading on a dark
+  scene does not band.
+- **Timestamps track a wall clock shared with the audio**, so a long screen and
+  audio recording stays in sync and finalises quickly instead of stalling. A
+  tick with no new frame behind it is skipped rather than re-encoded, so the
+  framerate in the file is the framerate that was captured.
+- **The container's frame timings are rewritten from those stamps** after the
+  file closes (2.1.125), because the fragmented-MP4 writer would otherwise give
+  the last frame of every fragment a fixed length and shorten still passages.