Every camera reserves a slice of its RAM for video buffers, and on most boards that
slice is larger than the pipeline needs. How it is reserved depends on the SoC:
HiSilicon uses MMZ over mem=/CMA, SigmaStar
uses a static MMA heap sized by U-Boot. The
streamer settings that decide how much the pipeline puts
into that region are the same settings on either, though the figures there were
measured on HiSilicon and Goke parts.
HiSilicon SoCs require contiguous physical memory for ISP and video DMA buffers.
This memory is managed by the MMZ (Media Memory Zone) subsystem inside the
open_osal kernel module.
There are three allocator backends:
| hisi (static) | cma (vendor) | cma (mainline) | |
|---|---|---|---|
| Available on | Kernel 4.9 | Kernel 4.9 | Kernel 6.6+ (neo) |
| How it works | Kernel is given a reduced mem= size; the remaining RAM is invisible to Linux and owned entirely by MMZ |
Kernel sees all RAM (mem=128M); vendor hi_cma.c manages a CMA region declared via bootargs; pages are reusable by the kernel when video is idle |
Kernel sees all RAM; mainline CMA reserves a region via Device Tree; pages are reusable by the kernel when video is idle |
| Memory efficiency | Low -- the MMZ region is wasted when video is idle (e.g. during boot, firmware updates, network-only workloads) | High -- CMA pages serve normal allocations and are migrated out on demand when video starts | High -- same as vendor CMA |
| Allocation latency | Deterministic -- buffers come from a dedicated pool that is always free | May spike -- CMA must migrate pages out before returning contiguous memory; first allocation can take a few milliseconds | May spike -- same as vendor CMA |
| Implementation | Simple range allocator over a hidden memory region | Vendor hi_cma.c integration with the kernel's CMA |
Kernel built-in CMA framework (dma_alloc_from_contiguous) |
| Configuration | mmz_allocator=hisi mmz=anonymous,... |
mmz_allocator=cma mmz=anonymous,... |
Device Tree reserved-memory node; no bootargs needed |
| Best for | Memory-constrained (64MB) boards where the kernel must fit in 32MB | 128MB boards on kernel 4.9 (default for hi3516ev300_lite) | 128MB+ boards on kernel 6.6+ (default for neo builds) |
On kernel 4.9, the hisi allocator hides memory from the kernel via mem=32M,
giving MMZ exclusive ownership of the remaining RAM. The cma allocator lets
the kernel see all RAM (mem=128M) and shares the MMZ region with normal
allocations via the vendor's hi_cma.c. Both are configured via U-Boot
bootargs.
On kernel 6.6+, the vendor hi_cma.c does not exist. Instead, the open-source
OSAL module uses the kernel's built-in CMA framework directly, configured
through the Device Tree. Functionally it behaves the same as vendor CMA --
the key improvement is that no bootargs are needed, the region is declared
in the Device Tree and the kernel configures everything automatically.
Rule of thumb: if your board has 128MB+ RAM, use the
cmaallocator (vendor CMA on 4.9, mainline CMA on 6.6+ -- both are the default). If you have 64MB RAM on kernel 4.9, usemmz_allocator=hisiwithmem=32M.
On the stock 4.9 kernel, MMZ uses a vendor-specific allocator controlled by U-Boot bootargs:
mem=32M ... mmz_allocator=cma mmz=anonymous,0,0x42000000,96M
mem=32Mgives the kernel 32MB; the rest (e.g. 96MB on a 128MB board) is reserved for video buffers.mmz_allocator=cmaselects the CMA-based allocator (requires vendorhi_cma.c).mmz=anonymous,0,0x42000000,96Mtells MMZ the physical address and size of the buffer region.
Boards with only 64MB of RAM typically use mem=32M with 32MB for MMZ. Boards
with 128MB use mem=32M with 96MB for MMZ.
Starting with the hi3516ev300_neo build (Linux 6.6 LTS), MMZ uses the
mainline kernel CMA (Contiguous Memory Allocator) configured via Device Tree.
No vendor bootargs are needed.
The kernel's Device Tree declares a reserved-memory region:
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
mmz: mmz {
compatible = "shared-dma-pool";
size = <0x6000000>; /* 96MB */
alloc-ranges = <0x40000000 0x8000000>;
reusable;
linux,cma-default;
};
};
The default CMA reservation is 96MB. This value was carried over from the 4.9
mem=32M split where "everything except 32MB" went to video. With CMA this
reasoning no longer applies — let's look at actual usage.
A typical hi3516ev300 streaming session with majestic (2592x1520 H.264 + MJPEG):
cat /proc/media-mem
# ---MMZ_USE_INFO:
# total size=98304KB(96MB), used=34396KB(33MB), remain=63908KB(62MB)
The video pipeline uses ~34MB. The remaining 62MB of the CMA region sits idle (though reusable by the kernel for movable pages).
Why does CMA size matter if pages are reusable?
CMA pages can only hold movable allocations (user memory, page cache). They
cannot hold non-movable allocations (kernel slab, page tables, DMA buffers,
kernel stacks). On a 128MB board with 96MB CMA, only 32MB is guaranteed
available for non-movable kernel allocations — effectively the same constraint
as the old mem=32M.
Reducing CMA gives the kernel more room for non-movable allocations:
| CMA size | Non-movable headroom | Video headroom | Status |
|---|---|---|---|
| 96MB (default) | 32MB | 62MB spare | Safe, but wastes non-movable headroom |
| 64MB | 64MB | 13MB spare | Tested, recommended for single-stream 4MP |
| 48MB | 80MB | Allocation failures | Too small for 2592x1520 streaming |
How to override CMA size without rebuilding:
The kernel accepts a cma= bootarg that overrides the Device Tree default:
# In U-Boot:
setenv bootargs mem=128M cma=48M console=ttyAMA0,115200 panic=20 ...
saveenvThe kernel will log bypass mmz node, using cmdline CMA params instead and
allocate a CMA pool of the specified size. You can verify:
# Verify the active CMA size
grep Cma /proc/meminfo
# CmaTotal: 49152 kB <-- 48MB
# CmaFree: 16384 kB
# Check if video has enough memory
cat /proc/media-mem
# If "used" is close to "total", increase the CMA size
# Monitor for CMA allocation failures
dmesg | grep -i "cma.*alloc.*failed"Recommendation: use
cma=64Mfor single-stream 4MP setups. This uses ~50MB for video with 13MB spare, and gives the kernel 64MB for non-movable allocations (double the default). If you see allocation failures in dmesg or majestic fails to start, remove thecma=parameter to fall back to the 96MB default.
128MB boards (hi3516ev300):
With 6.6, you can give the kernel the full 128MB (mem=128M or just remove the
mem= bootarg entirely). All three configurations work:
# Full memory, default CMA (recommended for most users)
mem=128M console=ttyAMA0,115200 ...
# Full memory, tuned CMA (recommended for advanced users)
mem=128M cma=48M console=ttyAMA0,115200 ...
# No mem= at all - kernel gets full RAM from DT
console=ttyAMA0,115200 ...
# Legacy bootargs still work (mmz params are silently ignored)
mem=128M ... mmz_allocator=cma mmz=anonymous,0,0x42000000,96M
64MB boards (hi3516ev200, hi3518ev300):
The 6.6 kernel with CMA has not yet been tested on 64MB boards. Use cma=24M
or cma=32M via bootargs if you are experimenting. For production, continue
using the 4.9 kernel with mem=32M.
Checking CMA status on a running system:
# Verify CMA reservation at boot
dmesg | grep -i cma
# Expected: "Reserved memory: created CMA memory pool at 0x42000000, size 96 MiB"
# Check CMA usage
grep Cma /proc/meminfo
# CmaTotal: 98304 kB
# CmaFree: 65536 kB
# Check MMZ allocations
cat /proc/media-memIf you are upgrading a hi3516ev300 from 4.9 to 6.6, update your U-Boot environment:
# Recommended: give kernel full memory, optionally tune CMA
setenv bootargs mem=128M console=ttyAMA0,115200 panic=20 ...
saveenv
Old bootargs with mmz_allocator=cma mmz=anonymous,... still work on 6.6 —
they are silently ignored. There is no need to change U-Boot before upgrading,
but you will benefit from updating mem= to 128M afterwards.
The sections above size the memory region. This one covers what the video 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. They differ from most in one
respect worth knowing: they decide how the video pipeline's buffers are laid
out, so changing one rebuilds the pipeline and every stream drops for a moment.
videoN.enabled is the exception — it is applied to its own channel alone, and
viewers of the other stream stay connected.
All of the region's usage is visible at runtime:
cat /proc/media-mem
# ---MMZ_USE_INFO:
# total size=98304KB(96MB),used=53032KB(51MB),remain=45272KB(44MB)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 — 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 |
no snapshots at all, and no frame reserved for them | one frame |
video1.enabled: false |
drops the second stream and its frame | one frame |
isp.yuvCompression: seg |
stores the pooled frames compressed | 2–6 MB, some SoCs |
jpeg.tuned |
caps parameterised snapshots to a size you choose | avoids a full frame |
isp.blkCnt |
overrides the frame count outright | one frame per step |
isp.memMode |
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.
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:
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 effectTwo things go into that figure. One block per encoding channel — video0, video1
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.
Both of those free their frame for the next start rather than immediately: the pool keeps whatever size it was given when the streamer came up, so switching a stream off on a running camera does not hand its block back. The figures above are what you measure after a restart. The same holds for idle suspension, which stops the sensor and ISP outright — a large power saving, and not a memory one at all.
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.
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.
/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:
cat /proc/umap/vb
# PoolId ... BlkCnt Free MinFree
# 0 5 4 1MinFree 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.
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.
curl 'http://localhost/api/v1/set?jpeg.enabled=false'Off means off, so be sure nothing you use needs it:
/image.jpganswers 503- the
/mjpegHTTP 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
/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:
# largest parameterised snapshot to serve; off (the default) refuses them
curl 'http://localhost/api/v1/set?jpeg.tuned=640x360'On an Ultimate build only width, height and qfactor still come this
way. crop and gray are cut out of the frame the camera has already made,
which reserves nothing and costs no quality — so if a smaller picture is what
you are after, a crop asks for one without any of the memory below. On Lite and
FPV all five parameters go through the second encoder.
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.
Defaults to reduction. Despite the name it does not shrink the frame
pool. It changes three things on the encoder side:
- Mini-buffer mode — the encoder works from smaller internal buffers.
- Smaller output buffers — the compressed-stream buffer is sized at about
half of what
normalreserves (with a floor, so very small streams still get a workable buffer). - Reference-frame sharing — the encoder reuses one buffer for reconstructed and reference frames instead of holding both.
normal reserves more headroom for all three. It is worth trying if you see
frames being dropped or re-encoded under a bitrate spike — for example with a
high-motion scene at a high bitrate — at the cost of a few megabytes.
curl 'http://localhost/api/v1/set?isp.memMode=normal'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.
curl 'http://localhost/api/v1/set?isp.yuvCompression=seg'auto (the default) leaves compression off, so nothing changes unless you ask
for it. Measured on a 128 MB board, comparing /proc/media-mem before and
after:
| SoC | Sensor | Before | After | Freed |
|---|---|---|---|---|
| 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. 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
rest reserve the full uncompressed size no matter what, on the basis that
compression is not guaranteed to achieve any particular ratio. On those parts
the frames are still compressed (less data crosses the memory bus, which is
useful in its own right) but /proc/media-mem will not move.
| SoC | Frees memory? |
|---|---|
| hi3516ev200, hi3516ev300, gk7205v200, gk7205v500 | Yes — 2 to 6 MB |
| hi3516cv500, hi3516av300 | No — reserved size is unchanged |
| hi3516cv300, hi3519v101 | No — marginally larger |
| hi3516av100, hi3518ev200, hi3516cv610 | Setting not available |
It is ignored when it would break something. Compression is silently incompatible with several features, so the streamer turns it off rather than let them fail, and logs the reason:
image.rotate(90 or 270) andimage.mirror— the pipeline cannot rotate or mirror a compressed frame. Note thatimage.flipis fine.osd.privacyMasks— privacy masks are not drawn on a compressed frame. This one matters: without the safeguard the stream would look correct while publishing the very area you meant to hide.- the raw
/image.yuv420and/image.webpendpoints — these read raw pixels, and nothing on these SoCs can turn a compressed frame back into pixels.
image.tuning is the one that yields the other
way round. It also needs raw pixels, but compression is not turned off for it:
the camera keeps the frames compressed as asked and switches automatic tuning
off instead, putting your saved picture settings back. Compressing the pool is
a memory decision on a board that may not have the memory, and it is not
overridden by a picture feature you can equally well leave off.
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, 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.
Kernel modules take about 5 megabytes of RAM (code with dynamic memory for buffers) and some of them are useless unless you need specific functionality like OSD, motion detection, audio, or specific codecs.
The table below shows vendor SDK module names, open-source module names (used by both 4.9 and 6.6 kernels in OpenIPC), and approximate sizes.
| Feature | Vendor SDK modules | OpenIPC modules | Size |
|---|---|---|---|
| Audio output | hi3516ev200_ao, hi3516ev200_adec | open_ao, open_adec | 78 KB |
| Audio input | hi3516ev200_ai, hi3516ev200_aenc | open_ai, open_aenc | 106 KB |
| Audio codec | hi3516ev200_acodec | open_acodec | 16 KB |
| Audio I/O (shared) | hi3516ev200_aio | open_aio | 33 KB |
| I2C sensor support | hi_sensor_i2c | open_sensor_i2c | 12 KB |
| SPI sensor support | hi_sensor_spi | open_sensor_spi | N/A |
| PWM support | hi_pwm | open_pwm | 12 KB |
| Motion detection | hi3516ev200_ive | open_ive | 188 KB |
| JPEG snapshots | hi3516ev200_jpege | open_jpege | 53 KB |
| H.264 codec | hi3516ev200_h264e | open_h264e | 131 KB |
| H.265 codec | hi3516ev200_h265e | open_h265e | 156 KB |
| Video encoder core | hi3516ev200_venc | open_venc | 274 KB |
| Rate control | hi3516ev200_rc | open_rc | 111 KB |
| Encode device | hi3516ev200_vedu | open_vedu | 29 KB |
| OSD / regions | hi3516ev200_rgn | open_rgn | 86 KB |
| ISP | hi3516ev200_isp | open_isp | 201 KB |
| Video input | hi3516ev200_vi | open_vi | 373 KB |
| VPSS | hi3516ev200_vpss | open_vpss | 332 KB |
| VGS | hi3516ev200_vgs | open_vgs | 143 KB |
| MIPI receiver | hi_mipi_rx | open_mipi_rx | 37 KB |
| Watchdog | hi_wdt | open_wdt | 12 KB |
To disable a module, comment out or remove the corresponding modprobe line in
/usr/bin/load_hisilicon. For example, to disable audio entirely, comment out
the insert_audio call and the individual modprobe open_aio, open_ai,
open_ao, open_aenc, open_adec, open_acodec lines.
Note:
open_osal,open_sys_config,open_base, andopen_sysare core modules required by all other subsystems and must not be removed.
SigmaStar SoCs put every ISP, scaler and encoder buffer in the MMA heap — the vendor multimedia allocator's pool, a contiguous region U-Boot carves out of DRAM before Linux starts. It is why a camera with 256 MB of RAM reports about 90 MB — this is what one owner's ssc377qe printed, in firmware#2443:
Memory: 89592K/262144K available (2404K kernel code, 347K rwdata, 1068K rodata,
124K init, 114K bss, 170504K reserved, 2048K cma-reserved)
Unlike HiSilicon's CMA-backed MMZ, MMA is a static carve-out. The pages are reserved whether or not video is running, and the kernel cannot borrow them back while the camera is idle. Shrinking the reservation is the only lever there is.
OpenIPC's U-Boot builds its bootargs from two variables
(include/configs/sstar-common.h):
... LX_MEM=${memlx} mma_heap=mma_heap_name0,miu=0,sz=${memsz} cma=2M
LX_MEM is how much DRAM Linux manages and sz is the MMA heap reserved
inside it. board_late_init() in arch/arm/cpu/armv7/infinity6c/chip.c — and
the matching infinity6, infinity6b0 and infinity6e files — fills both in
from the DRAM size it detects at boot:
infinity6, infinity6b0 and infinity6c share one set of sizes:
| DRAM | memsz (MMA heap) |
MemTotal that leaves |
|---|---|---|
| 256 MB | 0xA000000 — 160 MB |
91640 kB (measured, infinity6c) |
| 128 MB | 0x4600000 — 70 MB |
53848 kB (measured, infinity6c) |
| 64 MB | 0x2000000 — 32 MB |
~27 MB (arithmetic) |
infinity6e is the exception. It reserves slightly less, and it has rows for
512 MB and 1 GB that the others do not:
| DRAM | memsz (MMA heap) |
MemTotal that leaves |
|---|---|---|
| 1 GB | 0x1FE9C000 — 510 MB |
|
| 512 MB | 0x0FE9C000 — 255 MB |
|
| 256 MB | 0x9E9C000 — 158 MB |
92768 kB (measured) |
| 128 MB | 0x4E9C000 — 79 MB |
|
| 64 MB | 0x1E9C000 — 31 MB |
So two 256 MB cameras can report different totals — 91640 kB on an infinity6c
and 92768 kB on an infinity6e — purely because the families reserve 160 MB and
158 MB respectively. LX_MEM differs cosmetically too: infinity6c hands Linux
all of DRAM, the other three a fraction under it.
The Memory: line at the top of this section accounts for itself exactly — it
is a 256 MB infinity6c:
262144K DRAM, all of it given to Linux by LX_MEM=0x10000000
-163840K mma_heap sz=0x0A000000
- 2048K cma=2M, reported separately as "cma-reserved"
- 4057K kernel image (2404 code + 347 rwdata + 1068 rodata + 124 init + 114 bss)
- 2607K mem_map, page tables, early allocations
= 89592K available
MemTotal in /proc/meminfo is 91640 kB — the 89592K plus the 2 MB of CMA,
which counts as ordinary memory until something asks for a contiguous buffer.
cat /proc/mi_modules/mi_sys_mma/mma_heap_name0The first two lines are a header and a row for the heap. Read two of its
columns, both byte counts in hex: length, the size of the heap, and the
last column, how much of it is still free — named avail on infinity6c and
chunk_mgr_avail on infinity6e, depending on the firmware generation. What the
pipeline is using is the difference between them.
Everything below that row is one line per allocation, each attributed to the part of the video pipeline and the process holding it, which is how the figures below were measured.
Exercise the camera before you read it. A pipeline that has just started and never served a snapshot reports far less than its steady state. Pull a handful of stills and connect a client first.
Measured with majestic running its normal configuration:
| Board | Sensor and streams | Heap | Used | Spare |
|---|---|---|---|---|
| ssc377d (infinity6c, 128 MB) | imx335, video0 2560x1920 + JPEG | 70 MB | 40 MB | 30 MB |
| ssc30kq (infinity6e, 256 MB) | imx335, video0 full-res + video1 704x576 + JPEG 1280x720 | 158 MB | 56 MB | 102 MB |
A 5 MP pipeline needs something in the region of 40–56 MB. The 256 MB profile reserves about three times that.
memlx and memsz live in the saved environment, and fw_printenv will read
your value back happily — but board_late_init() calls setenv() on both
unconditionally, on every boot, before bootcmd runs. Anything you write
there is overwritten before the kernel command line is assembled:
Set memsz to anything you like and reboot, and all three of these disagree
with each other:
fw_printenv memsz # your value, still in flash where you wrote it
cat /proc/cmdline # the stock size, which is what the kernel was given
free # unchanged, because the heap never moved/proc/cmdline is the only honest answer of the three. Check it, and not the
environment, whenever you want to know how the heap is actually sized.
Rewrite bootargs itself so the size is literal. Substitute only the heap size
and leave every other token byte-identical — in particular leave
LX_MEM=${memlx} and ${rootmtd} as placeholders, so that mtdparts is never
retyped by hand:
fw_printenv -n bootargs > /root/bootargs.orig # keep the original
sed 's/\(mma_heap=[^ ]*,sz=\)[^ ,]*/\10x06000000/' /root/bootargs.orig > /tmp/ba
diff /root/bootargs.orig /tmp/ba # must show sz= and nothing else
fw_setenv bootargs "$(cat /tmp/ba)"
fw_printenv -n bootargs # read it back BEFORE rebooting
rebootTo undo it: fw_setenv bootargs "$(cat /root/bootargs.orig)".
Copy
bootargs.origoff the camera as well.firstboot, and anysysupgrade -n, wipe the overlay that/rootlives on — while the U-Boot environment you just changed survives untouched. That is the one combination that leaves a camera booting a value you can no longer look up.
Run the diff, do not skip it. The substitution matches the sz= inside
mma_heap whatever it holds, so it works on an installation whose bootargs
still carries ${memsz} and on an older one that was set up with a literal size
and mma_memblock_remove=1. But a bootargs shaped differently again will pass
through unchanged, fw_setenv will write it back happily, and the camera will
reboot with the size it already had. An empty diff means nothing was replaced.
The bootargs stored in the environment keep their ${...} placeholders; U-Boot
expands them at boot through setenv setargs setenv bootargs ${bootargs}; run setargs, which is why substituting one of them works and why the rest can be
left alone.
Every byte taken off the heap goes back to Linux, one for one:
| Board | Heap | MemTotal |
Heap still free |
|---|---|---|---|
| ssc30kq, 256 MB, stock | 158 MB | 92768 kB | 102 MB |
ssc30kq, 256 MB, sz=0x06000000 |
96 MB | 156880 kB | 40 MB |
| ssc377d, 128 MB, stock | 70 MB | 53848 kB | 30 MB |
ssc377d, 128 MB, sz=0x3800000 |
56 MB | 68184 kB | 16 MB |
Both kept streaming, served snapshots at full sensor resolution and logged no allocation failures.
Take the measured peak and leave real headroom over it — on a 256 MB board
sz=0x06000000 (96 MB) is a reasonable starting point for a single 5 MP sensor,
and on a 128 MB board sz=0x3800000 (56 MB).
Measure with everything you actually use switched on. The heap is sized once,
at startup, and overlays, a second stream, LDC and parameterised snapshots all
come out of it. The streamer settings that decide
how much the pipeline puts into the region apply here as much as on HiSilicon —
video1.enabled, jpeg.enabled and jpeg.tuned are each worth a whole frame.
Running short does not fail loudly. The vendor allocator returns an error deep inside the stack, so the symptom is a stream that never starts or a snapshot that is never answered — and it can appear only at a later mode change, such as a night-mode switch or a resolution change, rather than at boot. Keep the original
bootargssomewhere you can paste it back from, and test the camera through a full day/night cycle before you trust a new value.