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:
- 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.- VideoToolbox low-latency mode is transformative: H.264 4K30 goes 140.1 ms → 16.4 ms.
- AV1 encode does not exist on this hardware — proven by
-12908 kVTCouldNotFindVideoEncoderErr, not assumed.- 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:
| Resolution | Frame-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 class | Main sensor | Full-res dimensions | Approx HEIF |
|---|---|---|---|
| iPhone 12 / 13 / 14 non-Pro | 12 MP | 4032 x 3024 | ~2 MB |
| iPhone 14 Pro and later, all 15/16/17 | 48 MP | 8064 x 6048 | ~5 MB |
| iPhone 14 Pro+ via deferred delivery | 24 MP | 5712 x 4284 | ~3 MB |
| All iPads | 12 MP | 4032 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 MPrenders 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
| Effect | App-settable? |
|---|---|
Center Stage (centerStageControlMode) | ✅ Yes |
| Portrait | ❌ class, readonly |
| Studio Light | ❌ class, readonly |
| Reactions | ❌ class, readonly |
| Background Replacement | ❌ class, 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
AVCaptureDeviceTypeExternalon macOS 26.5. The SDK header comment claims they reportBuiltInWideAngleCamera. 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.
| Claim | Reality |
|---|---|
| ”Photo Booth is stuck at 640x480” | False. That limitation is long gone. |
| How it configures capture | Uses AVCaptureDeviceFormat directly — no sessionPreset |
| Actual ceiling | The camera’s native format — i.e. whatever macOS exposes |
| Virtual cameras | Accepted — OBS Virtual Camera appears in its Camera menu |
| Effects | 5 pages x 8, non-destructive, with File > Export Original… |
| Storage | ~/Pictures/Photo Booth Library |
| 4-up mode | Four 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:
| Codec | Format | Normal | Low-latency |
|---|---|---|---|
| H.264 | 1080p60 | 37.0 ms | 23.2 ms |
| H.264 | 4K30 | 140.1 ms | 16.4 ms |
| HEVC | 1080p60 | 43.9 ms | 32.0 ms |
| HEVC | 4K30 | 98.1 ms | 19.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 kVTParameterErrDecode only. Use HEVC or H.264.
4. Core Image on a Metal-backed context
60 iterations after warmup:
| Operation | 1080p | 4K |
|---|---|---|
| Copy only (the floor) | 0.465 ms | 1.062 ms |
| ColorControls + TemperatureAndTint + ExposureAdjust + ToneCurve | 0.506 ms | 1.025 ms |
32³ LUT (CIColorCubeWithColorSpace) | 0.663 ms | 1.241 ms |
CIPersonSegmentation fast | 2.297 ms | 3.536 ms |
CIPersonSegmentation accurate | 29.700 ms | 30.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
CIPersonSegmentationin 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
AVCaptureVideoPreviewLayerin anNSViewRepresentable, 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
CMIOExtensionMachServiceNameimplying otherwise. Apple DTS calls that key “a red herring.”Why it fails: the extension runs as
_cmiodalassistants, bundle typeSYSX, and its sandbox forbids global mach service registration. You get:NSCocoaErrorDomain Code=4099
The sanctioned path: a sink stream
The extension publishes two streams:
| Stream | Direction | Consumer |
|---|---|---|
| Source stream | out | Apps (Zoom, OBS, Photo Booth…) |
| Sink stream | in | The host app pushes frames into it |
Implemented through the CMIO C API — CMIOStreamCopyBufferQueue + 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
| Trap | Detail |
|---|---|
| DAL plugins | Deprecated macOS 12.3, actually disabled in Sonoma 14.1 |
| Container app location | Must be in /Applications or activation fails with error 3 |
| Multiple versions | Only one version active per team; a version bump needs a reboot |
| Client visibility | Client apps must be restarted to see a newly installed virtual camera |
Entitlements
Extension:
com.apple.security.app-sandbox— mandatory- 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:
| App | Team ID | Signing | Sandbox |
|---|---|---|---|
| OBS Studio | 2MMRE5MTB8 | Developer ID, no MAS receipt | Unsandboxed container |
| Camo Studio | Q248YREB53 | Developer ID, no MAS receipt | Unsandboxed container |
- Camo uses
com.apple.security.temporary-exception.sbplto 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.
Related
- aposemati — project overview, decisions, and phase plan
- aposemati-transport-findings — Wi-Fi Aware verdict and the AWDL reality check
- camo-studio-teardown — competitive teardown of Camo Studio 2.8.2
- diy-continuity-camera-clone-research — superseded original brief
- aposemati-phase0-phase1-build — the build that beat the Continuity ceiling by 17x
- aposemati-apple-signing-gotchas — getting the apps onto real devices
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.
sipssilently misreports HEIC orientation — do not use it
sips -g orientationreports<nil>for files ImageIO reads asOrientation = 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
mdlsor 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
.photopreset 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. SetsessionPreset = .inputPriorityand chooseactiveFormatexplicitly.- 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.
CameraDescriptormust carry the delivered video size, not just the still size — the zoom ceiling isdeliveredLongEdge / outputLongEdgeand is a property of the camera.videoRotationAngleForHorizonLevelPreviewis for a preview layer. For anAVCaptureVideoDataOutputconnection 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 withsettingPropertiesat 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 thesettingPropertiesroute writes it. CGImageDestinationAddImageFromSourcedrops the HDR gain map while retagging.- CoreImage can carry a gain map through a crop: read with
CIImage(data:options: [.auxiliaryHDRGainMap: true]), write withCIImageRepresentationOption.hdrGainMapImage. Costs ~70 ms and ~1% of file size;Headroomround-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
SmallVectoroverflow in the type checker, with a stack trace and no diagnostic. Split into named sub-views. DragGesture.translationis 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 computestart + translation.Synchronization.Mutexis~Copyable, so a struct holding one stops being copyable and can no longer be passed by value. EveryMutexholder in this project is afinal class.