Skip to content
C.W.K.
Lesson 01 of 05 · published

The Recorder Owns the Devices

~14 min · avfoundation, swift, single-owner, tcc, architecture

Level 0Closed Lens
0 XP0/45 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

Three Processes, One of Them Touches Hardware

Oriel is three programs. The engine is Python and FastAPI: sessions, settings, the frame index, the gate, every look and every app action. The page is a React build the engine serves. The recorder is a small Swift 6 app, OrielRecorder.app, with no window (it runs as a background UI element), and it is the only one of the three that ever opens a device. Invariant 8 puts it flatly: only OrielRecorder opens a camera, and the engine cannot open a camera or a sound at all.

One narrow exception stands beside that rule. Two small native probes, an HDMI audio probe and a frame-rate probe, open a device briefly for their one job. Each asks the engine before anything opens (the eyes gate for video, a deck check for audio) and refuses, with exit code 7, when the engine says no, does not answer, or was never named. The recorder is the only long-running owner; the probes are short visits that the engine has to wave through.

That is a strong rule for a system whose whole purpose is cameras. The engine decides what should be recorded, when a frame is needed and whether it may be read. The recorder is the only thing with hands on the hardware.

Why One Owner

Three separate pressures pushed the design to a single owner, and any one of them would have been enough.

  • The grant. As the previous track showed, macOS grants the camera to one signed program in one GUI session. Every additional process that wants a camera would need its own grant, its own signing, its own place in that session. One owner means one grant to give, one row to check in System Settings, one thing to revoke.
  • The device. A capture card is not a file several readers can share politely. Opening it, choosing a format and a frame rate, and starting a capture session are stateful acts. Two processes opening the same card at once either fight over its format or one of them fails. A single owner can serve a recording, a one-frame peek and a gimbal move for the same device without a fight, because it is the one deciding.
  • The guard. The consent machinery in track five only works if there is exactly one path to a device to guard. If the engine could open a camera directly, a bug in the engine could skip the gate. Because it cannot, the gate stands on the one channel to the recorder, and a test scans the repository for any other way to a camera (an avfoundation ffmpeg input, a UVC control tool, a write to the recorder's state file from somewhere unexpected).

What the Recorder Does

The recorder is deliberately boring. It discovers every video device AVFoundation can see: external devices (capture cards, USB webcams), an iPhone as a Continuity Camera (the source comment below also names the iPad, but macOS offers Continuity Camera only on iPhone), a built-in camera. It reports them, with every mode each one offers, about once a second. When asked, it opens a device by its unique ID, never by name or by index, because two identical capture cards share a name and AVFoundation's device order can differ between processes. It records each open input as HEVC through VideoToolbox into one-minute segment files, keeps a fresh JPEG of each input's newest frame, takes one-shot frames (peeks) when asked, and sends gimbal moves to the webcams that have motors, inside a running stream.

It does not decide anything about privacy. It does not know what a role is, which camera faces where, or whether Dad said yes. Those are the engine's questions, answered before the recorder is asked. Keeping the native app small has a practical payoff too: rebuilding and re-signing it is the risky step (a new build must keep its camera grant), so the less often it changes, the better. Of Oriel's first 368 commits, 298 touched the engine's backend and 11 touched the recorder's sources.

Sound Has an Owner Too, and It Is Not the Recorder

The one sound Oriel records, a deck card's HDMI audio, follows the same rule with a twist. The microphone grant belongs to a separate signed probe, not to the recorder. When a deck card records, the recorder starts that probe as a one-shot launchd job and reads its samples over a local socket. If the recorder spawned the probe as its own child, macOS would ask whether the recorder may use the microphone, and the clean separation (one program per grant) would be gone. So the camera grant and the microphone grant live in two different signed programs, each the single owner of its kind of device.

Code

How the recorder finds devices, and why it never trusts a name (trimmed excerpt, native/OrielRecorder/Sources/OrielRecorder/Devices.swift)·swift
import AVFoundation
import CoreMedia

/// Every video device macOS shows to AVFoundation: capture cards and webcams
/// (`.external`), an iPhone or iPad as a Continuity Camera, a built-in camera.
/// Known by `uniqueID`, never by name: two [capture cards of one model] share a model.
enum Devices {
    static func discovery() -> [AVCaptureDevice] {
        let session = AVCaptureDevice.DiscoverySession(
            deviceTypes: [.external, .continuityCamera, .builtInWideAngleCamera],
            mediaType: .video, position: .unspecified)
        return session.devices
    }

    static func device(uid: String) -> AVCaptureDevice? {
        discovery().first { $0.uniqueID == uid } ?? AVCaptureDevice(uniqueID: uid)
    }
}
List your own Mac's video devices the same way (save as list.swift, run with: swift list.swift)·swift
import AVFoundation

let devices = AVCaptureDevice.DiscoverySession(
    deviceTypes: [.external, .continuityCamera, .builtInWideAngleCamera],
    mediaType: .video, position: .unspecified).devices

for device in devices {
    let formats = device.formats.count
    print("\(device.localizedName) | uniqueID \(device.uniqueID) | \(formats) formats")
}
print("authorization: \(AVCaptureDevice.authorizationStatus(for: .video).rawValue)")
// Listing devices needs no camera grant; opening one does. Run it from a
// terminal app that holds the grant, then from an ssh session, and compare.

External links

Exercise

Run the Swift listing script from this lesson on a Mac with at least one webcam or capture card. Note each device's uniqueID. Then unplug one USB camera, plug it into a different port, and run the script again. Did its uniqueID change?
Hint
For many USB video devices the uniqueID encodes the port, so it changes with the port. That observation is the whole of lesson four in this track: a unique ID turns out to be a place.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.