Everything on this page was measured on hardware or read out of SDK headers on 2026-08-18 (macOS 26.5 / 26.5.1, Apple Silicon, Xcode 26.3) — none of it is recalled or inferred. These are the numbers aposemati’s design leans on.

For Agents

Four load-bearing results, if you read nothing else:

  1. Continuity Camera tops out at 1920x1440, 8-bit 420v. There is no 4K path. Beating it on quality is trivially achievable — that is the product opening.
  2. VideoToolbox low-latency mode is transformative: H.264 4K30 goes 140.1 ms → 16.4 ms.
  3. AV1 encode does not exist on this hardware — proven by -12908 kVTCouldNotFindVideoEncoderErr, not assumed.
  4. XPC to a CoreMediaIO camera extension does not work. Use a sink stream. Apple DTS calls CMIOExtensionMachServiceName “a red herring.”

1. Continuity Camera’s hard ceiling

Enumerated on iPhone 16 Pro and iPhone 13 Pro Max. Both produce exactly 8 formats, all 8-bit 420v:

ResolutionFrame-rate ranges
640 x 480@1–30, @1–60
1280 x 720@1–30, @1–60
1920 x 1080@1–30, @1–60
1920 x 1440@1–30, @1–60

The iPhone 16 Pro gives you nothing the 13 Pro Max doesn't

Max is 1920x1440. No 4K. No 10-bit. A newer phone does not raise the ceiling — the ceiling is in Continuity Camera itself, not the sensor. This is why on-device capture (rather than consuming Continuity) is the whole point of aposemati.

The claim is now proven, not argued — 2026-08-18

The same iPhone 16 Pro delivered 8064x6048 = 48.8 MP to a Mac through Aposemati. Against the 1920x1440 ceiling above that is ~17x the pixels, over a link that never touched the router.

1a. Device capability matrix — 48 MP starts at the iPhone 14 Pro

Correction — the design originally said "48 MP" everywhere

That was wrong and had to be replaced with a per-device capability model. Nothing may hardcode a resolution; the capture device reports its formats at pairing and the host UI presents what that device can actually do.

Device classMain sensorFull-res dimensionsApprox HEIF
iPhone 12 / 13 / 14 non-Pro12 MP4032 x 3024~2 MB
iPhone 14 Pro and later, all 15/16/1748 MP8064 x 6048~5 MB
iPhone 14 Pro+ via deferred delivery24 MP5712 x 4284~3 MB
All iPads12 MP4032 x 3024~2 MB

Consequences carried into the build:

  • No iPad has a 48 MP sensor — which is why iPad is a host, not a capture device.
  • The 24 MP mode is only serviced as 24 MP when opted in to autoDeferredPhotoDeliveryEnabled. Aposemati opts out, so capture is 12 MP or 48 MP and there is no PhotoKit dependency and no photo-library permission on the phone.
  • “48MP” appears in no AVFoundation header. Only the 24 MP / 5712x4284 mode is gated behind deferred delivery — an earlier worry that 48 MP needed deferred delivery was unfounded.
  • UI trap: 8064 x 6048 = 48.77 MP renders as “49 MP” under naive rounding. The 12-vs-48 distinction is the entire point of the matrix.

Effect controls: only Center Stage is app-settable

EffectApp-settable?
Center Stage (centerStageControlMode)✅ Yes
Portraitclass, readonly
Studio Lightclass, readonly
Reactionsclass, readonly
Background Replacementclass, readonly
Edge Light (new in macOS 26.2, undocumented)class, readonly

Undocumented find: “Edge Light” appears in macOS 26.2 and is not in Apple’s documentation.

Device-type reporting gotcha

NSCameraUseContinuityCameraDeviceType in Info.plist makes connected phones report AVCaptureDeviceTypeContinuityCamera.

The SDK header comment appears to be wrong

Without that key, phones report AVCaptureDeviceTypeExternal on macOS 26.5. The SDK header comment claims they report BuiltInWideAngleCamera. Observed behaviour contradicts the header. Trust the observation.

2. Photo Booth is NOT low-res any more

A widely repeated “fact” that is now stale. Photo Booth 13.1 (build 1177) on macOS 26.5 was inspected directly.

ClaimReality
”Photo Booth is stuck at 640x480”False. That limitation is long gone.
How it configures captureUses AVCaptureDeviceFormat directlyno sessionPreset
Actual ceilingThe camera’s native format — i.e. whatever macOS exposes
Virtual camerasAccepted — OBS Virtual Camera appears in its Camera menu
Effects5 pages x 8, non-destructive, with File > Export Original…
Storage~/Pictures/Photo Booth Library
4-up modeFour separate JPEGs plus a compiled 2x2 composite

Why this matters for Aposemati

Photo Booth’s non-destructive effects + Export Original… is precisely the “framing is metadata, never pixels” model aposemati adopts. Apple already validated the pattern in a shipping first-party app.

3. VideoToolbox — low-latency mode is the headline

Queue-to-callback latency, Apple Silicon, macOS 26.5:

CodecFormatNormalLow-latency
H.2641080p6037.0 ms23.2 ms
H.2644K30140.1 ms16.4 ms
HEVC1080p6043.9 ms32.0 ms
HEVC4K3098.1 ms19.2 ms

H.264 4K30 improves by 8.5x. Low-latency mode is not a tuning knob, it is a different regime.

  • macOS 26 added kVTCompressionPreset_VideoConferencing, reachable only through the low-latency encoder. That is the preview-encoder configuration.

AV1 encode does not exist

Proven by attempted session creation, not inferred:

  • without low-latency → -12908 kVTCouldNotFindVideoEncoderErr
  • with low-latency → -12902 kVTParameterErr

Decode only. Use HEVC or H.264.

4. Core Image on a Metal-backed context

60 iterations after warmup:

Operation1080p4K
Copy only (the floor)0.465 ms1.062 ms
ColorControls + TemperatureAndTint + ExposureAdjust + ToneCurve0.506 ms1.025 ms
32³ LUT (CIColorCubeWithColorSpace)0.663 ms1.241 ms
CIPersonSegmentation fast2.297 ms3.536 ms
CIPersonSegmentation accurate29.700 ms30.659 ms

A full colour grade is indistinguishable from a memory copy

The entire chain costs 0.506 ms against a 0.465 ms copy-only floor at 1080p. It is essentially free. Do not hand-write Metal for exposure, white balance, curves, or LUTs. There is nothing to win.

Accurate segmentation is a hard 33 fps ceiling

CIPersonSegmentation in accurate mode costs ~30 ms regardless of resolution — 1080p and 4K are within 1 ms of each other — because the model runs at a fixed internal size. Resolution is not the lever; mode is. Fast mode is 2.3 ms, ~13x cheaper.

5. There is no SwiftUI camera view

A search of the macOS 26.2 SwiftUI interface — 675 public structs — returns zero camera, capture, video, or player symbols.

Preview therefore means one of:

  • wrapping AVCaptureVideoPreviewLayer in an NSViewRepresentable, or
  • rendering sample buffers through AVSampleBufferDisplayLayer.

6. CoreMediaIO camera extension gotchas

For the deferred virtual-camera phase (phase 7 of aposemati).

XPC to a camera extension DOES NOT WORK

Despite CMIOExtensionMachServiceName implying otherwise. Apple DTS calls that key “a red herring.”

Why it fails: the extension runs as _cmiodalassistants, bundle type SYSX, and its sandbox forbids global mach service registration. You get:

NSCocoaErrorDomain Code=4099

The sanctioned path: a sink stream

The extension publishes two streams:

StreamDirectionConsumer
Source streamoutApps (Zoom, OBS, Photo Booth…)
Sink streaminThe host app pushes frames into it

Implemented through the CMIO C APICMIOStreamCopyBufferQueue + CMSimpleQueueEnqueue.

AVFoundation has no output-device support

There is no AVFoundation-level way to write into a virtual camera. You must drop to the CMIO C API.

Lifecycle and deployment traps

TrapDetail
DAL pluginsDeprecated macOS 12.3, actually disabled in Sonoma 14.1
Container app locationMust be in /Applications or activation fails with error 3
Multiple versionsOnly one version active per team; a version bump needs a reboot
Client visibilityClient apps must be restarted to see a newly installed virtual camera

Entitlements

Extension:

  • com.apple.security.app-sandboxmandatory
  • App group

Container app:

  • com.apple.developer.system-extension.install
  • App group
  • Camera + audio device

7. Distribution: Developer ID + notarization, not the Mac App Store

Both shipping competitors were inspected and both agree:

AppTeam IDSigningSandbox
OBS Studio2MMRE5MTB8Developer ID, no MAS receiptUnsandboxed container
Camo StudioQ248YREB53Developer ID, no MAS receiptUnsandboxed container
  • Camo uses com.apple.security.temporary-exception.sbpl to reach /private/var/run/usbmuxd — the peertalk / USB path. That exception is MAS-hostile; it is a concrete reason the App Store is not an option for this class of app.
  • System extensions signed with Developer ID MUST be notarized to load.

Decision for Aposemati

Developer ID + notarization. The Mac App Store is off the table for anything that ships a CMIO system extension or touches usbmuxd. See camo-studio-teardown.


Measured 2026-08-26 — crop, format selection, image metadata

All from an iPhone 16 Pro on iOS 26.6 and macOS 26.x. Context: aposemati-crop-and-framing.

sips silently misreports HEIC orientation — do not use it

sips -g orientation reports <nil> for files ImageIO reads as Orientation = 6.

That output was written into a design spec as evidence for a backwards conclusion — that the phone rotates stills by pixels, when in fact it tags them. Left standing it would have put every zoomed crop 90° from where it was framed, while the file still looked upright in viewers, because viewers apply the tag.

Use mdls or ImageIO. Third tool-trust failure on this project, and the first to reach a spec. See Tools that lie.

The phone tags stills, it does not rotate their pixels. All nine captures on the Mac carry a non-identity tag — Orientation = 6 on eight rear stills, 5 on the front one, {TIFF} matching each time. Depth = 8 on every one (so the sensor is not 10-bit, and a re-encode loses no bit depth). HDR gain map present on seven of eight — the front camera has none.

Format selection

  • .photo preset picks a slow format. It selected a 17 fps format on the iPhone 16 Pro while a 4032×3024 @ 30 fps format offering 8064×6048 stills sat unused. Set sessionPreset = .inputPriority and choose activeFormat explicitly.
  • Rank on video pixels, not just frame rate. Ranking by max stills, then max fps picked a 192×144 video format for the front camera, because it offered 60 fps and tied on stills. The result was a postage stamp stretched across the viewfinder. Rank: max stills → ≥30 fps → largest video buffer → highest fps.
  • CameraDescriptor must carry the delivered video size, not just the still size — the zoom ceiling is deliveredLongEdge / outputLongEdge and is a property of the camera.
  • videoRotationAngleForHorizonLevelPreview is for a preview layer. For an AVCaptureVideoDataOutput connection use …ForHorizonLevelCapture; they differ by 90° in portrait. The angle must also go on the photo output connection, or the viewfinder is horizon-levelled and the file it produces is not.

Core Image and HEIC

  • CIImage(data:) does not apply the EXIF orientation tag. Crop against it and you crop stored pixels while your rectangle means the levelled frame.
  • After .oriented(_:) the properties still carry the old tag, so a naive re-encode writes rotated pixels plus a tag telling viewers to rotate again. Re-stamp with settingProperties at both the top level and inside {TIFF}, which carries its own copy.
  • Setting orientation through heifRepresentation(options:) silently does not work — the file still comes out tagged. Only the settingProperties route writes it.
  • CGImageDestinationAddImageFromSource drops the HDR gain map while retagging.
  • CoreImage can carry a gain map through a crop: read with CIImage(data:options: [.auxiliaryHDRGainMap: true]), write with CIImageRepresentationOption.hdrGainMapImage. Costs ~70 ms and ~1% of file size; Headroom round-trips to six significant figures. The map is half resolution and must be oriented and cropped with the primary’s tag and from the oriented extents.
  • HEIC fixtures cannot give exact pixel equality in tests — encode-then-rotate versus rotate-then-encode differ by up to 59/255 per channel through HEVC. Use PNG fixtures where a test needs byte equality.

SwiftUI and Swift

  • A view written as one expression can crash the Swift compiler — a SmallVector overflow in the type checker, with a stack trace and no diagnostic. Split into named sub-views.
  • DragGesture.translation is cumulative from the gesture’s start. Adding it to a value that has already absorbed earlier translations compounds the movement. Capture the origin at gesture start (@GestureState, which cannot leak a stale origin past an interrupted drag) and compute start + translation.
  • Synchronization.Mutex is ~Copyable, so a struct holding one stops being copyable and can no longer be passed by value. Every Mutex holder in this project is a final class.