From de7b91efdd9574df2d7cb2e6d37528f13f8be925 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:46:23 +0300 Subject: [PATCH 1/2] memory-tuning: the frame pool sizes itself, so say what it arrives at MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isp.blkCnt was written up as a number to tune by hand, one step at a time, watching whether the stream survived. The streamer now derives it from the pipeline, so the useful thing to document is what it arrives at and why two cameras with the same chip land on different figures — which turns out to be the sensor's width rather than the model of SoC, since a sensor too wide for the capture pipe has to route frames through memory and that costs whole frames. Also documents how running short presents, because it is the least intuitive part: a pool one block short encodes video perfectly and answers no snapshots, so a camera judged by its RTSP feed looks healthy. And how to read the headroom from /proc/umap/vb rather than guessing at it. Drops the advice to pair compression with a lower blkCnt. The streamer works out how many frames it can compress while keeping the rest of the pipeline supplied, and says when there is nothing to spare. Drops the "a recent majestic update" framing from the compression section. What the software does now is the documentation; when it started doing it is not. --- en/memory-tuning.md | 128 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 20 deletions(-) diff --git a/en/memory-tuning.md b/en/memory-tuning.md index 2583d9b2..ec8171ba 100644 --- a/en/memory-tuning.md +++ b/en/memory-tuning.md @@ -229,22 +229,97 @@ Most of `used` is frame buffers: a pool of full-size video frames that the ISP, the scaler and the encoder pass between them. A 1080p frame is about 3 MB and a 5 MP frame about 5.6 MB, so the pool dominates everything else on the board. +The streamer sizes that pool itself, from the pipeline your configuration asks +for. The first setting below explains what it arrives at and when it is worth +overriding; the other two change what goes into the pool rather than how much +of it there is. + #### `isp.blkCnt` — how many frames are pooled -The number of frames reserved for the pipeline. Lowering it frees whole frames -at a time, which makes it the biggest single lever — and the easiest one to -overshoot: too few buffers and the encoder starves, producing a stream that -stalls or never starts. +The number of frames reserved for the pipeline, and the biggest single lever on +the board — each one is a whole frame. + +**You do not normally need to set it.** The streamer works out how many the +pipeline holds and reserves that plus one spare. It logs the arithmetic on every +start: + +```bash +logread | grep 'VB sizing' +# VB sizing: 4 blocks for the pipeline (2 of them held by the VI path), +# default 5, isp.blkCnt 5 in effect +``` + +Two things go into that figure. One block per encoding channel — video0, video1 +if enabled, and the JPEG/MJPEG channel. Plus whatever the capture path holds: +nothing if it feeds the scaler directly, two blocks if it writes raw frames +through memory, four if it writes processed frames through memory. + +Which of those applies is decided by the sensor's width, not by the model of +SoC, and it is why two cameras with the same chip can need different amounts: + +| Camera | Capture path | Blocks it holds | Pipeline total | +|---|---|---|---| +| gk7205v200 + 1920x1080 sensor | straight through | 0 | 3 | +| hi3516ev300 + 2592x1520 sensor | raw through memory | 2 | 4 | +| hi3516av300 + 3840x2160 sensor | processed through memory | 4 | 6 | + +A sensor wider than the capture pipe's limit — 2304 pixels on most parts — has +to go through memory, and that costs whole frames. It is the single largest +reason a 5 MP camera needs more than a 1080p one, beyond the frames being bigger. + +##### When you would override it + +```bash +curl 'http://localhost/api/v1/set?isp.blkCnt=6' +``` + +Raise it if you serve many snapshots at once, or run something that holds frames +for longer than the pipeline expects. Lower it only if you are short of memory +and willing to test — and know what running short looks like, because it is not +obvious: + +> **A pool one block short does not stop the video.** The stream encodes +> perfectly and snapshots return nothing at all. Anyone judging the camera by +> its RTSP feed will conclude it is healthy. + +Nothing fails when you set it too low — the pool is reserved exactly as asked +and every call succeeds — so the streamer checks the figure against what the +pipeline needs and says when it does not reach: + +``` +VB short: 3 blocks for a pipeline that holds 4 — expect snapshots to go +unanswered while video keeps running. Raise isp.blkCnt, or lower the +resolution or the stream count +``` + +Setting it too high is handled rather than fatal. If the reservation does not +fit the region, the streamer falls back to its own computed figure instead of +taking everything that will fit — the frame pool is the only allocation big +enough to starve the encoders, and one that swallows the region leaves a camera +that starts, reports no errors and streams nothing. + +##### Checking the headroom you actually have + +`/proc/umap/vb` records the low-water mark of every pool, so you can see how +close the camera came to running out rather than guessing: ```bash -curl 'http://localhost/api/v1/set?isp.blkCnt=4' +cat /proc/umap/vb +# PoolId ... BlkCnt Free MinFree +# 0 5 4 1 ``` -Change it one step at a time and confirm the stream still runs. If you also -enable compression below, be more conservative here: compression takes some of -these buffers for itself, so the two together go further than either alone. On -a 5 MP hi3516ev300, `isp.blkCnt=4` on its own is fine and `isp.blkCnt=4` with -compression stops snapshots working. +`MinFree` is the fewest blocks that have been free at any point since boot. `1` +means the pool never had more than one to spare. `0` means every block was in +use at once, so anything asking for one more at that moment went without — which +is survivable, and is what a camera running at exactly its computed figure looks +like, but leaves nothing for a burst. + +Exercise the camera before reading it — take several snapshots, connect a client +— since a pool that has done nothing yet reports plenty free. + +The same file attributes each block to the subsystem holding it, which is how +the table above was measured. #### `isp.memMode` — what "reduction" actually changes @@ -268,9 +343,9 @@ curl 'http://localhost/api/v1/set?isp.memMode=normal' #### `isp.yuvCompression` — compressing the frame pool -A recent majestic update can store the pipeline's frames **compressed** in -memory. Where the SoC allows it, the pool is then reserved at the compressed -size and the difference is returned to the system. +The pipeline's frames can be stored **compressed** in memory. Where the SoC +allows it, the pool is then reserved at the compressed size and the difference +is returned to the system. ```bash curl 'http://localhost/api/v1/set?isp.yuvCompression=seg' @@ -285,11 +360,22 @@ after: | hi3516ev300 | 2592x1520 | 53032 KB | 47108 KB | **5.8 MB** | | gk7205v200 | 1920x1080 | 21560 KB | 19460 KB | **2.1 MB** | -Roughly 1 MB per pooled frame at 1080p and 2 MB at 5 MP, for two or three -frames depending on `isp.blkCnt`. Your absolute figures will differ — total -usage depends on resolution, second stream, snapshots and the rest of the -configuration — so compare the **difference** across the restart rather than -matching the numbers above. +Roughly 1 MB per pooled frame at 1080p and 2 MB at 5 MP. Your absolute figures +will differ — total usage depends on resolution, second stream, snapshots and +the rest of the configuration — so compare the **difference** across the restart +rather than matching the numbers above. + +Only the main encoding channel reads compressed frames; everything else in the +pipeline still needs ordinary ones. The streamer works out how many frames it +can move to the compressed pool while keeping the rest supplied, so this does +not need pairing with a lower `isp.blkCnt`. If there is nothing to spare it +leaves the pool alone and says so, and you get the compression without the +saving: + +``` +VB split skipped: 4 blocks available (3 must stay linear), seg 3927 B vs +linear 5771 B +``` **Not every chip benefits.** The saving comes from reserving less, and only some SoCs' buffer arithmetic will reserve less for a compressed frame — the @@ -321,9 +407,11 @@ let them fail, and logs the reason: Overlays, timestamps, motion detection, digital image stabilisation and low delay all work normally alongside it. -To confirm it took effect, compare `/proc/media-mem` before and after. If the numbers do not move, either the SoC is one of those that -reserves the full size anyway, or one of the settings above overrode it — the +To confirm it took effect, compare `/proc/media-mem` before and after. If the +numbers do not move, the SoC is one of those that reserves the full size anyway, +the pool had nothing to spare, or one of the settings above overrode it — the log says which. + --- ### Disable subsystems you don't use From 00f3269ad601830a76a9692aeaa6d1a351d4ab2e Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Wed, 5 Aug 2026 08:00:29 +0300 Subject: [PATCH 2/2] memory-tuning: lead with the settings that return whole frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The section explained how to tune the frame pool before saying that the cheapest way to shrink it is to stop asking for channels you do not use. Every channel costs a whole frame whether or not it is producing one, because the pool is fixed when the streamer starts — so jpeg.enabled and video1.enabled each return more than any amount of tuning below them, and on a 24MB board one frame is an eighth of everything. So the section now opens with a table of what each setting is worth, in frames rather than in prose, and the two JPEG settings get their own entries: jpeg.enabled, which no longer half-works — it used to reserve a frame for a channel it would not raise, and answer /image.jpg anyway. Off now means no frame and a named refusal, and the article says plainly that this takes ONVIF snapshot URIs and the web UI preview with it, since that is the part someone will discover the hard way. jpeg.tuned, which is new: a size rather than a switch, because the memory follows it. Parameterised snapshots need a second encoder, and reserving for the size you actually fetch costs about 1MB at 640x360 against 5.6MB for a sensor frame. Includes the sizes where a cap stops being a saving. --- en/memory-tuning.md | 111 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 7 deletions(-) diff --git a/en/memory-tuning.md b/en/memory-tuning.md index ec8171ba..26ba574f 100644 --- a/en/memory-tuning.md +++ b/en/memory-tuning.md @@ -210,7 +210,7 @@ but you will benefit from updating `mem=` to `128M` afterwards. ### Streamer memory settings The sections above size the memory *region*. This one covers what the video -pipeline puts **into** it, and the three settings that change how much it takes. +pipeline puts **into** it, and the settings that change how much it takes. These are applied like any other setting, through the HTTP API described in [Majestic streamer](majestic-streamer.md). They differ from most in one @@ -230,9 +230,21 @@ the scaler and the encoder pass between them. A 1080p frame is about 3 MB and a 5 MP frame about 5.6 MB, so the pool dominates everything else on the board. The streamer sizes that pool itself, from the pipeline your configuration asks -for. The first setting below explains what it arrives at and when it is worth -overriding; the other two change what goes into the pool rather than how much -of it there is. +for — one frame per encoding channel, plus what the capture path holds. So the +largest savings come from telling it about channels you do not want, rather than +from tuning the pool directly: + +| Setting | What it does | Typical saving | +|---|---|---| +| [`jpeg.enabled: false`](#jpegenabled--snapshots-and-the-mjpeg-stream) | no snapshots at all, and no frame reserved for them | **one frame** | +| [`video1.enabled: false`](#one-frame-per-channel) | drops the second stream and its frame | **one frame** | +| [`isp.yuvCompression: seg`](#ispyuvcompression--compressing-the-frame-pool) | stores the pooled frames compressed | 2–6 MB, some SoCs | +| [`jpeg.tuned`](#jpegtuned--parameterised-snapshots) | caps parameterised snapshots to a size you choose | avoids a full frame | +| [`isp.blkCnt`](#ispblkcnt--how-many-frames-are-pooled) | overrides the frame count outright | one frame per step | +| [`isp.memMode`](#ispmemmode--what-reduction-actually-changes) | encoder-side buffers, not the pool | a few MB | + +A frame is about 3 MB at 1080p, 5.6 MB at 5 MP and 11.9 MB at 4K, so on a small +board one of these is worth more than all the tuning below it. #### `isp.blkCnt` — how many frames are pooled @@ -249,10 +261,19 @@ logread | grep 'VB sizing' # default 5, isp.blkCnt 5 in effect ``` +##### One frame per channel + Two things go into that figure. One block per encoding channel — video0, video1 -if enabled, and the JPEG/MJPEG channel. Plus whatever the capture path holds: -nothing if it feeds the scaler directly, two blocks if it writes raw frames -through memory, four if it writes processed frames through memory. +if enabled, and the JPEG/MJPEG channel if `jpeg.enabled` is on. Plus whatever +the capture path holds: nothing if it feeds the scaler directly, two blocks if +it writes raw frames through memory, four if it writes processed frames through +memory. + +Every channel costs a whole frame whether or not it is producing one at the +moment, because the pool is fixed when the streamer starts — a channel raised on +demand still needs its block sitting there waiting. That is why switching a +channel off is worth more than any amount of tuning: `video1.enabled: false` and +`jpeg.enabled: false` each return a full frame. Which of those applies is decided by the sensor's width, not by the model of SoC, and it is why two cameras with the same chip can need different amounts: @@ -321,6 +342,82 @@ Exercise the camera before reading it — take several snapshots, connect a clie The same file attributes each block to the subsystem holding it, which is how the table above was measured. +#### `jpeg.enabled` — snapshots and the MJPEG stream + +Controls whether the camera serves JPEG at all, and it is the single biggest +saving available on a small board: with it off, no frame is reserved for the +snapshot channel. + +```bash +curl 'http://localhost/api/v1/set?jpeg.enabled=false' +``` + +Off means off, so be sure nothing you use needs it: + +- `/image.jpg` answers **503** +- the `/mjpeg` HTTP stream answers "MJPEG is unavailable" +- the RTSP JPEG track is left out of the stream description + +That includes **ONVIF snapshot URIs and the web UI preview**, which both fetch +`/image.jpg`. If a home-automation integration pulls stills from this camera, +leave it on. Video over RTSP is unaffected either way. + +Measured, on cameras with one h264 stream and no snapshot ever taken: + +| SoC | Sensor | Pool with JPEG on | Off | Freed | +|---|---|---|---|---| +| gk7205v200 | 1920x1080 | 12156 KB | 9116 KB | **3.0 MB** | +| hi3516ev300 | 2592x1520 | 28860 KB | 23088 KB | **5.6 MB** | + +On a gk7205v200, whose whole media region is 24 MB, that is an eighth of it. + +The startup log states both halves, so the saving is never a mystery and neither +are the refusals: + +``` +JPEG off: no block reserved for it, and /image.jpg and the MJPEG stream will +be refused. Set jpeg.enabled to true to restore them +``` + +#### `jpeg.tuned` — parameterised snapshots + +`/image.jpg` accepts `width`, `height`, `qfactor`, `gray` and `crop`, which are +served by a **second** snapshot encoder on its own geometry. That encoder needs +frames of its own, so it is off by default: + +```bash +# largest parameterised snapshot to serve; off (the default) refuses them +curl 'http://localhost/api/v1/set?jpeg.tuned=640x360' +``` + +The value is a size rather than a switch because the memory follows it. Frames +are reserved for the size you name, not for the sensor — at 5 MP a full frame is +5.6 MB, where three 640x360 frames come to 1013 KB: + +| `jpeg.tuned` | Reserved on a 5 MP camera | +|---|---| +| `off` (default) | nothing; requests refused **503** | +| `640x360` | ~1.0 MB | +| `1280x720` | ~4.0 MB | +| `1920x1080` | ~8.9 MB — more than a full frame; prefer a crop | + +Pick the largest picture you actually fetch. Anything above the size is refused +**400** naming the cap, rather than quietly served from the pipeline's own +frames: + +``` +snapshot is larger than jpeg.tuned allows; ask for a smaller size or raise +jpeg.tuned and restart +``` + +A request at or above the sensor's own resolution is always refused, whatever +the cap: the full frame is already going to video0 and the MJPEG channel, and a +third copy of it is never produced. Ask for a smaller size, or use `crop` to cut +a region at 1:1. + +Plain `/image.jpg` with no parameters does not need this setting — it is served +by the MJPEG channel that `jpeg.enabled` already pays for. + #### `isp.memMode` — what "reduction" actually changes Defaults to `reduction`. Despite the name it does **not** shrink the frame