Skip to content

Commit 3ab6717

Browse files
committed
feat: add bounded asynchronous remote image jobs
1 parent 6f052d6 commit 3ab6717

25 files changed

Lines changed: 1085 additions & 10 deletions
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Home media allocation budgets
2+
on:
3+
push:
4+
branches: [main]
5+
paths: ['src/home/budget_allocator.zig', 'src/home/zlib_stream.zig', '.github/workflows/home-media-budget.yml']
6+
pull_request:
7+
paths: ['src/home/budget_allocator.zig', 'src/home/zlib_stream.zig', '.github/workflows/home-media-budget.yml']
8+
workflow_dispatch:
9+
permissions:
10+
contents: read
11+
jobs:
12+
budget:
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15]
17+
zig: ['0.15.2', 'master']
18+
runs-on: ${{ matrix.os }}
19+
steps:
20+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
21+
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29
22+
with:
23+
version: ${{ matrix.zig }}
24+
- name: Native zlib dependency
25+
shell: bash
26+
run: |
27+
set -euo pipefail
28+
if [ "$RUNNER_OS" = macOS ]; then
29+
brew install zlib
30+
echo "ZLIB_TEST_PREFIX=$(brew --prefix zlib)" >> "$GITHUB_ENV"
31+
else
32+
sudo apt-get update && sudo apt-get install -y zlib1g-dev
33+
echo "ZLIB_TEST_PREFIX=/usr" >> "$GITHUB_ENV"
34+
fi
35+
- name: Budget accounting ownership transfer and native workspace failure cleanup
36+
shell: bash
37+
run: |
38+
set -euo pipefail
39+
for mode in Debug ReleaseSafe; do
40+
zig test -O "$mode" src/home/budget_allocator.zig
41+
zig test -O "$mode" -lc -lz -L"$ZLIB_TEST_PREFIX/lib" -rpath "$ZLIB_TEST_PREFIX/lib" src/home/zlib_stream.zig
42+
done

‎NOTICE‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,3 +655,10 @@ Home exact typed scalar reads (0.91):
655655
- src/home/catalog_projection.zig is verbatim Home video/core catalog projection at
656656
e260034eebf954ad99415ea04bdb117d560bae15. Camera owns the request schema,
657657
source-session lifetime and process admission; no Home OS source is copied.
658+
659+
- src/home/budget_allocator.zig and zlib_stream.zig are verbatim from Home
660+
e499d6e91b89296bb0a47e763452240467817c56. Native zlib workspace now uses the
661+
caller allocator; the existing Bun ABI attribution in zlib_stream remains.
662+
- src/home/apng_encoder.zig is updated verbatim from Home
663+
0349186f0cc38bdf6be2a714f04ae0a6ae543b2a, counting native workspace in the
664+
height-independent allocation test. Camera owns native job/lease/file policy.

‎README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ AVC/HEVC/AAC adapters, containers and bounded media queues. No FFmpeg or Python
66
runtime, bindings, test scripts or build scripts are used by this project.
77
Physical camera performance and image quality have **not been measured**.
88

9+
Version 0.93 adds [asynchronous remote still encoding and saves](docs/remote-async.md)
10+
through bounded native Home codec workers. Jobs own copied pixels/Exif/ICC before
11+
source closure; joined cancellation and staged publication preserve destinations.
12+
Shared Home allocator budgets also cover native zlib workspace.
13+
914
Version 0.92 adds [request-owned configuration](docs/request-configuration.md)
1015
for source frames, retained exports and remote/process frames. Snapshots include
1116
requested and negotiated geometry, formats, color, sensor settings and orientation;

‎build.zig.zon‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.{
22
.name = .zig_camera,
3-
.version = "0.92.0",
3+
.version = "0.93.0",
44
.fingerprint = 0x774c3582060c5787,
55
.minimum_zig_version = "0.15.2",
66
.dependencies = .{},

‎docs/home-os-reuse.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,12 @@ hosted process configuration snapshot to import. Home stdlib therefore gains the
540540
bounded exact catalog projection used here. Native camera session negotiation,
541541
lease ownership and source-request provenance stay in zig-camera. No OS source
542542
is changed or executed, and no stub success is counted as hardware acceptance.
543+
544+
The 0.93 asynchronous-still audit fetches unchanged clean Home OS main at
545+
151269666bc09aa15a271f1c1f4603f681132e65 and inspects camera.home,
546+
media/video_decoder.home, fs/journal.home and ADR-MC3. The capture/codec stubs and
547+
kernel journal ownership do not supply a hosted copied-image worker or joined
548+
user-file publication primitive. Shared allocation budgets and zlib workspace
549+
ownership therefore improve Home stdlib; existing camera staged files retain
550+
hosted identity/publication/cleanup policy. No OS source is copied, changed or
551+
executed, and no kernel result is treated as camera or performance evidence.

‎docs/home-reuse.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,3 +1615,29 @@ existing owned full metadata and its bounded accounting; no second fd, cache,
16151615
process initialization dependency or channel packet version is introduced. Home
16161616
OS's current capture/configuration stub cannot supply an owned request snapshot;
16171617
see the separate OS audit. No Python or FFmpeg is added or executed.
1618+
1619+
## Asynchronous remote stills (0.93)
1620+
1621+
Fetched Home main at e260034eebf954ad99415ea04bdb117d560bae15 and inspected Home
1622+
video admission/preparation, image raster/Exif codecs, streaming PNG/APNG and
1623+
memory allocators. New shared budget_allocator at e499d6e91b89296bb0a47e763452240467817c56
1624+
adds allocation-free live/peak/denial accounting, atomic failed growth and explicit
1625+
same-parent result ownership transfer. Zlib now routes native workspace through
1626+
the supplied allocator, including partial initialization failure cleanup. Five
1627+
budget and four zlib tests pass pinned/development Zig in both modes; the budget
1628+
helper cross-links actual Linux ARM/x86 ELF tests on both compiler lines.
1629+
1630+
The first full camera test exposed an APNG scratch test that deliberately excluded
1631+
native workspace under the old allocator contract. Home follow-up
1632+
0349186f0cc38bdf6be2a714f04ae0a6ae543b2a replaces that assumption with exact total
1633+
allocation equality for height 1 versus 20000, including all native workspace and
1634+
complete frees. APNG/raster dependents pass both compilers/modes. All three modules
1635+
are copied verbatim. Upstream exact-head Actions are unavailable; consumer budget
1636+
and independent APNG platform matrices provide separate hosted evidence.
1637+
1638+
Camera reuses Home admission, preparation gates, format conversion, calibrated
1639+
DNG and request Exif/ICC encoders. A joined worker owns copied physical pixels and
1640+
metadata; existing staged-file ownership controls publication. No frame/map/fd
1641+
is borrowed by the worker, and no manager callback performs this work. No second
1642+
codec or Python/FFmpeg tooling is added. Process termination/publication and full
1643+
file-like semantics remain explicit open work, distinct from cooperative cleanup.

‎docs/parity-plan.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ manual review before completion. Recheck upstream changes during the final audit
2525
| Encoders/recording | Home MJPEG, AVC, HEVC, AAC, MKV/MP4; named software JPEG/AVC quality tiers and a shared HEVC rate heuristic with exact rational planning, limits admission and session propagation; repeated in-band parameter sets on packet keyframes with immutable first-only opt-out across outputs and sessions; counted public IDR requests with synchronous/async producer ordering, cancellation and per-session accounting; copied AVC profile ceilings, validated codec presets and fixed-QP session policies with Baseline/Main container configuration; mixed tiny/4K HEVC with shared CTU32 and visible conformance crops; copied decoder-level ceilings and VBV/CPB policies with nominal resource/actual-header checks and explicit coordinated resets; copied session color profiles with matching matrix/range conversion and ISO/VUI/nclx across native/Bun outputs, snapshots, restart/reconfiguration and coordinated modes; main/RGB-BGR-RGBX-BGRX-RGB16-BGR16-I420-YVU-NV-422 lores encoder sources, compact producer ownership, shared encoding and session transactions, bounded independent encoders with wait/drop policy and shared PCM retention/catch-up and coordinated all-active encoder mode transactions | Complete upstream quality/rate-control behavior, additional pixel formats/raw input, general gamut/transfer conversion, MJPEG/Matroska color metadata options, independent cadence/resampling and physical shared-audio acceptance, hardware encoder paths where available, timestamps and metadata inputs |
2626
| Output families | File/custom output objects, circular/delayed A/V, PTS, split/rotation/recovery, bounded async I/O | Remaining upstream buffering/flush semantics, muxer/network/process-output equivalents through Home/Bun, full close/reopen/failure coverage |
2727
| Scheduling/extensibility | Bounded Bun jobs, cancellation, completion callbacks, drains, joined native ownership, exact frame dropping/timestamp waits, scoped pre/post callbacks with main-thread mode-worker handoff and explicit retained acquisition | Complete arbitrary dispatch/wait/cancel/flush semantics and remaining callback/allocator integration behavior |
28-
| Memory/process boundaries | Retained pointer-free descriptor exports with duplicated fds, explicit fd-less staging, full typed metadata and one whole-frame gate; bounded actual SCM_RIGHTS channels, cached shared catalogs, checked receiver-owned maps, explicit ACK primitives, request-owned configuration across source/export/remote/process boundaries, remote owned image copies, compact/shaped/physical/raw16 copies, calibrated DNG and processed saves with request-derived Exif/ICC encoding, and opt-in scoped write planes; supervised actual Bun process pools with native moved-export/argument/result admission, matched ACK/result routing, verified-exit cancellation, isolated failures, generations and callback-aware prefix drains; persistent DMA maps, bounded retained requests, scoped read/shared and write/exclusive native mappings; opt-in Bun write contexts with detached views on close | Allocator/persistent-allocation equivalents, remaining remote conveniences, richer value and automatic recovery semantics, remaining IPC/allocator behavior; GPU integration remains unimplemented |
28+
| Memory/process boundaries | Retained pointer-free descriptor exports with duplicated fds, explicit fd-less staging, full typed metadata and one whole-frame gate; bounded actual SCM_RIGHTS channels, cached shared catalogs, checked receiver-owned maps, explicit ACK primitives, request-owned configuration across source/export/remote/process boundaries, bounded joined remote asynchronous still encoding/staged saves with owned metadata, remote owned image copies, compact/shaped/physical/raw16 copies, calibrated DNG and processed saves with request-derived Exif/ICC encoding, and opt-in scoped write planes; supervised actual Bun process pools with native moved-export/argument/result admission, matched ACK/result routing, verified-exit cancellation, isolated failures, generations and callback-aware prefix drains; persistent DMA maps, bounded retained requests, scoped read/shared and write/exclusive native mappings; opt-in Bun write contexts with detached views on close | Allocator/persistent-allocation equivalents, remaining remote conveniences, richer value and automatic recovery semantics, remaining IPC/allocator behavior; GPU integration remains unimplemented |
2929
| Camera-specific integrations | Generic typed libcamera controls | IMX708 helpers, IMX500 tensor/network/inference/ROI workflows, Hailo integration and postprocessing equivalents; hardware-backed validation |
3030
| Hardware/quality/performance | Synthetic ownership/media tests; Linux x86/ARM real-backend build/load/no-camera smoke; independent decoders | Matched Pi camera benchmarks, sensor/optics controls, sustained capture/preview/video, disconnects, physical A/V synchronization, multi-camera and device-specific acceptance |
3131

@@ -141,3 +141,9 @@ remote process workers, using bounded Home catalog projection without another
141141
transport version or fd; see request-configuration.md. Full property snapshots,
142142
async/file-like publication, allocator and automatic recovery, optional
143143
integrations and physical acceptance remain open. This does not complete parity.
144+
145+
Version 0.93 adds bounded native remote image encode/save jobs and asynchronous
146+
DNG with source-independent input, Home workspace budgets, copied Exif/ICC and
147+
joined staged publication/cancellation; see remote-async.md. Full property
148+
snapshots, file-like and parent-controlled process publication/cleanup, allocator
149+
and recovery semantics, optional integrations and physical acceptance remain open.

‎docs/process-pools.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,11 @@ across retained exports and actual child processes, including reused workers
185185
after mode switches. Startup context remains independently owned and unchanged.
186186
Full per-request property catalogs, remaining remote conveniences and the broader
187187
parity families remain open.
188+
189+
Version 0.93 adds [asynchronous remote still jobs](remote-async.md): copied input,
190+
bounded native Home encoding with Exif/ICC, byte results and staged processed/DNG
191+
saves. Await those jobs inside a handler before returning. Cooperative job
192+
cancellation joins native encoding and disposal; supervisor termination remains
193+
an external boundary and cannot undo published files or guarantee removal of a
194+
named temporary owned by a killed process. Parent-controlled publication/cleanup
195+
and file-like destinations remain open.

‎docs/remote-async.md‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Asynchronous remote stills
2+
3+
Version 0.93 adds Bun `RemoteFrame.encodeImageAsync`, `saveAsync` and
4+
`saveDngAsync`, plus C/Zig native image encoding jobs. They cover main/lores
5+
JPEG, PNG, TIFF, BMP, PPM and GIF and calibrated raw DNG through Home codecs.
6+
7+
```ts
8+
const job = remote.saveAsync('trail.jpeg', {
9+
quality: 98,
10+
maxInputBytes: 64 * 1024 * 1024,
11+
maxOutputBytes: 64 * 1024 * 1024,
12+
maxWorkingBytes: 128 * 1024 * 1024,
13+
})
14+
remote.close() // Admission already owns pixels and needed metadata.
15+
// Low-level callers still acknowledge/retire their source export separately.
16+
await job
17+
```
18+
19+
Admission synchronously copies selected physical used bytes (including used row
20+
padding), requested ICC/Exif and any metadata needed for request-derived Exif.
21+
The worker owns no camera, lease, remote map, fd or caller metadata pointer.
22+
Conversion, metadata generation, compression and file staging run on the native
23+
worker. Owned encoded bytes and completed files outlive source closure. Bun owns
24+
option snapshots before admission and rechecks private read permission after all
25+
application accessors. Writable remote scopes reject admission.
26+
27+
`encodeImageAsync` accepts the synchronous image options, byte limits and ordinary
28+
Bun job options. `saveAsync(path, options, stream)` preserves the existing main/
29+
lores save signature and extension inference. `saveDngAsync(path, jobOptions)`
30+
selects raw. JPEG still defaults to source-request Exif; PNG/TIFF opt in with
31+
`exif: true`, and explicit fields override defaults. ICC interpretation checks
32+
and RGB16 precision follow the synchronous encoders. These methods do not infer
33+
values from the process pool's initialization camera.
34+
35+
## Bounds and ownership
36+
37+
Input/output defaults are 64 MiB, working memory 128 MiB. Each limit accepts
38+
1 byte through 256 MiB. Input counts copied physical pixels, ICC, raw Exif and needed full
39+
metadata. Working memory counts live allocator requests, including conversion,
40+
metadata, encoded bytes and native zlib workspace. Native zlib uses the caller's
41+
allocator with checked aligned allocation hooks and failure-safe partial cleanup.
42+
An output ceiling can reject after compression; the working budget also applies
43+
while the output is being built. Allocator denial reports NOMEM; input/output
44+
limit violations report INVALID.
45+
46+
Four live jobs share a 512 MiB input-plus-working reservation (including owners);
47+
byte limits can admit fewer than four. Completed handles stay charged until
48+
close. Bounds cover owned payload and allocator requests, not thread stacks,
49+
allocator bookkeeping, source storage or application-retained returned results.
50+
C state reports input bytes, encoded output size, working peak and budget denials;
51+
working/output telemetry is published only after the worker finishes.
52+
53+
C `zc_remote_frame_encode_async` accepts optional limits and an optional existing
54+
staged-file owner. Poll state, optionally cancel, then take a byte result once or
55+
finish the staged file. Keep that file alive until image-job close. Close returns
56+
BUSY until the worker finishes, consumes only on success and joins before freeing
57+
input/workspace. Zig `ImageEncodeJob` preserves that retryable ownership. Native
58+
callers serialize API entries against close.
59+
60+
Cancellation is cooperative between codec stages; an active codec call completes
61+
before cleanup joins. Bun remains responsive during native encoding. An accepted
62+
cancellation/deadline suppresses file publication and joins job/file disposal
63+
before terminal completion and callbacks. Native staged files retain inode checks
64+
against substitution. Publication claims the irreversible decision before rename;
65+
late cancellation can be refused. Existing destinations survive admission,
66+
encoding, budget, cancellation and staging failures.
67+
68+
A process handler must await its image jobs before returning. Process termination
69+
is an external boundary: files already published cannot be rolled back, and a
70+
process killed while owning a named temporary may leave that temporary behind.
71+
Parent-controlled cross-process publication/cleanup and file-like destinations
72+
remain open. These cooperative job guarantees do not claim rollback after SIGKILL.
73+
74+
Software coverage includes all 14 processed layouts and 46 raw layouts, exact
75+
synchronous/async bytes, independent DNG/Exif/ICC readback, early source retirement,
76+
actual child processes, input/output/workspace denial, completion-held admission,
77+
native result transfer, event-loop responsiveness, deadline/cancellation cleanup,
78+
callbacks and accessor reentry. Full parity and physical Pi acceptance remain open.
79+
80+
Local validation passes 445 native ReleaseSafe tests with an explicit macOS 15
81+
target and all 1,418 Bun tests with Home codecs/AAC, independent media readers and
82+
SDL dummy. Independent C ABI/export/channel/process exec checks, TypeScript,
83+
pinned formatting and workflow YAML validation pass. The normal local build
84+
runner still fails macOS SDK/libSystem linkage before project compilation; its
85+
failure is retained separately. Hosted platform, real-backend and Home/APNG
86+
consumer checks remain independent publication gates.

‎include/zig_camera.h‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,25 @@ int zc_save_stream_image_metadata(zc_camera *,uint64_t token,uint32_t stream,con
623623
* No durability/fsync guarantee. Crash cleanup of orphan temporaries is external. */
624624
typedef struct zc_image_file zc_image_file;
625625
typedef struct { uint32_t struct_size,phase,preparing,released,finished,committed; int32_t result,cleanup_code; } zc_image_file_state;
626+
/* Bounded owned still encoding. Copies selected physical pixels, ICC/Exif and
627+
* needed metadata at admission; no remote owner/lease/fd survives that call.
628+
* Codec conversion/encoding and optional file staging run on a joined worker.
629+
* Limits are per-job: input includes copied metadata, working includes ALL
630+
* allocator-routed codec allocations and the encoded result, output caps bytes.
631+
* Four live jobs share a 512 MiB input+working reservation, held until close.
632+
* Cancel is cooperative at codec boundaries; close BUSY means join by polling.
633+
* File publication remains explicit through image_file_finish AFTER completion;
634+
* retain that file owner until the job closes. Null limits uses defaults. */
635+
typedef struct zc_image_job zc_image_job;
636+
typedef struct {uint32_t struct_size,reserved;uint64_t max_input_bytes,max_output_bytes,max_working_bytes;} zc_image_job_limits;
637+
typedef struct {uint32_t struct_size,phase,finished;int32_t result;uint64_t input_bytes,output_bytes,working_peak,budget_denials;} zc_image_job_state;
638+
void zc_default_image_job_limits(zc_image_job_limits *);
639+
int zc_remote_frame_encode_async(const zc_remote_frame *,uint32_t stream,const zc_image_options *,const zc_image_profile *,const zc_image_exif *,const zc_image_job_limits *,zc_image_file *,zc_image_job **out);
640+
int zc_image_job_state_get(zc_image_job *,zc_image_job_state *);
641+
int zc_image_job_cancel(zc_image_job *);
642+
int zc_image_job_take_blob(zc_image_job *,zc_image_blob **out);
643+
int zc_image_job_close(zc_image_job *);
644+
626645
/* Factory admission is globally bounded to 32 retained handles, including ready
627646
* and finished handles. BUSY preserves a null output; close releases the slot
628647
* after joining. This also bounds threads for rejected async file submissions. */

0 commit comments

Comments
 (0)