Enhancing live video by leveraging TrueDepth camera data
At a glance
| Item |
Summary |
| Purpose |
Synchronize color, depth, and face metadata; derive a foreground mask; blend a chosen background; and render with Metal. |
| App architecture |
Storyboard UIKit controller owns the end-to-end capture and Core Image pipeline; a custom MTKView owns display rendering. |
| Main patterns |
MVC, AVFoundation delegate, synchronized stream aggregation, queue confinement, and renderer encapsulation. |
| Project style |
One screen controller, one reusable preview view, and a small Metal shader folder. |
Project structure
TrueDepthBackdrop/
├── CameraViewController.swift # UI, session, synchronization, image processing
├── PreviewMetalView.swift # Thread-safe image handoff and Metal render loop
├── Shaders/
│ └── PassThrough.metal # Vertex and fragment functions
├── Base.lproj/Main.storyboard # View hierarchy and outlets
└── AppDelegate.swift
Structure observations
CameraViewController.swift is deliberately broad: it is the feature controller, capture owner, delegate, and image-processing coordinator.
- GPU presentation is separated into
PreviewMetalView, which hides Metal objects and draw-loop state.
- There is no view model, service protocol, or separate domain layer.
Overall architecture
flowchart LR
Storyboard["Storyboard"] --> Controller["CameraViewController"]
Controller --> Session["AVCaptureSession"]
Session --> Outputs["Video + depth + face outputs"]
Outputs --> Sync["AVCaptureDataOutputSynchronizer"]
Sync --> Controller
Controller --> CI["Depth mask + Core Image blend"]
CI --> Preview["PreviewMetalView"]
Preview --> Metal["Metal pipeline + shaders"]
Reference code
TrueDepthBackdrop/CameraViewController.swift:407 and TrueDepthBackdrop/CameraViewController.swift:622 — synchronized callback boundary
outputSynchronizer = AVCaptureDataOutputSynchronizer(
dataOutputs: [videoDataOutput, depthDataOutput, metadataOutput]
)
outputSynchronizer!.setDelegate(self, queue: dataOutputQueue)
func dataOutputSynchronizer(
_ synchronizer: AVCaptureDataOutputSynchronizer,
didOutput synchronizedDataCollection: AVCaptureSynchronizedDataCollection
) { /* build the mask and composited image */ }
Interpretation
The view controller is the application-level owner and receives one synchronized collection per processing step. It transforms the depth buffer into a matte, blends the color frame with the selected background, then hands a CIImage to the specialized renderer.
Ownership and state
classDiagram
Storyboard *-- CameraViewController : instantiates
CameraViewController *-- AVCaptureSession : creates
CameraViewController *-- AVCaptureVideoDataOutput : creates
CameraViewController *-- AVCaptureDepthDataOutput : creates
CameraViewController *-- AVCaptureMetadataOutput : creates
CameraViewController *-- AVCaptureDataOutputSynchronizer : creates
CameraViewController o-- PreviewMetalView : weak outlet
PreviewMetalView *-- CIContext : creates
PreviewMetalView *-- MTLCommandQueue : creates
Ownership evidence
TrueDepthBackdrop/CameraViewController.swift:18 — controller-owned graph and storyboard-owned view
@IBOutlet weak private var previewView: PreviewMetalView!
private let session = AVCaptureSession()
private let sessionQueue = DispatchQueue(label: "session queue")
private let dataOutputQueue = DispatchQueue(label: "video data queue")
private let videoDataOutput = AVCaptureVideoDataOutput()
private let depthDataOutput = AVCaptureDepthDataOutput()
private let metadataOutput = AVCaptureMetadataOutput()
| Owner |
Object or state |
Relationship |
Mutation authority |
| Storyboard/view hierarchy |
Controller and PreviewMetalView |
Instantiates and retains UI |
UIKit lifecycle |
CameraViewController |
Session, inputs, outputs, synchronizer, device |
Creates and retains |
Controller, with session mutations on sessionQueue |
CameraViewController |
Background image and mask tuning values |
Retains |
Controller callbacks and image-picker flow |
PreviewMetalView |
Internal image snapshot, transforms, Metal resources |
Receives image; creates renderer state |
View, synchronized through syncQueue |
Class and protocol design
TrueDepthBackdrop/CameraViewController.swift:14 — framework callback composition
class CameraViewController: UIViewController,
AVCaptureDataOutputSynchronizerDelegate,
UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
// ...
}
| Type |
Responsibility |
Depends on |
CameraViewController |
Capture lifecycle, authorization, interruptions, synchronized processing, background selection |
UIKit, AVFoundation, Core Image |
PreviewMetalView |
Accept a CIImage, snapshot thread-shared state, and render a textured quad |
MetalKit, Core Image |
AVCaptureDataOutputSynchronizerDelegate |
Deliver aligned video/depth/metadata collections |
AVFoundation protocol |
| Image-picker/navigation delegates |
Deliver a replacement background |
UIKit protocols |
This is protocol-oriented at framework callback boundaries, not through app-defined capabilities.
Access control
| Symbol |
Access |
Verified effect |
Likely rationale |
| Controller outlets |
private weak |
Only the controller accesses them; UIKit view hierarchy retains the views. |
Accurately expresses storyboard ownership and avoids a second strong owner. |
SessionSetupResult and setup state |
private |
Configuration outcomes cannot become an app-wide state contract. |
Keeps recovery policy local to the screen. |
| Session, devices, outputs, queues, synchronizer |
private |
No collaborator can mutate the capture graph directly. |
Protects thread and configuration invariants. |
| Preview’s internal image/mirroring/rotation and Metal objects |
private |
Callers set only image, mirroring, and rotation. |
Separates a small input surface from render-loop state. |
| Controller delegate methods |
internal by default |
Framework/runtime can invoke conforming witnesses; app module can also see them. |
Required protocol and Objective-C runtime integration. |
Reference code
TrueDepthBackdrop/PreviewMetalView.swift:21 — narrow input with private synchronized copy
var image: CIImage? {
didSet {
syncQueue.sync { internalImage = image }
}
}
private var internalImage: CIImage?
private let syncQueue = DispatchQueue(label: "Preview View Sync Queue")
Logic ownership and placement
| Logic |
Owning type or file |
Placement rationale |
| Authorization, start/stop, interruption recovery |
CameraViewController |
Screen and session lifecycle are coupled |
| Capture graph construction and depth-format selection |
configureSession() |
One private mutation boundary for the session |
| Align video, depth, and face data |
synchronizer delegate method |
Framework delivers all inputs together |
| Depth cutoff, blur/gamma matte, background blend |
synchronizer delegate method |
Per-frame pipeline operates on aligned buffers |
| Image-to-texture rendering and transforms |
PreviewMetalView |
Renderer-specific, independent of capture configuration |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| MVC/controller |
TrueDepthBackdrop/CameraViewController.swift:14 |
One controller coordinates the screen and feature; simple but large. |
| Delegate |
TrueDepthBackdrop/CameraViewController.swift:622 |
Receives aligned output without polling. |
| Stream aggregator |
TrueDepthBackdrop/CameraViewController.swift:407 |
Synchronizer combines three capture outputs into one processing unit. |
| Thread confinement |
TrueDepthBackdrop/CameraViewController.swift:36, TrueDepthBackdrop/CameraViewController.swift:43 |
Separates blocking session work from real-time data processing and UI. |
| Encapsulated renderer |
TrueDepthBackdrop/PreviewMetalView.swift:12, TrueDepthBackdrop/PreviewMetalView.swift:267 |
Hides Metal setup and draw mechanics behind image/rotation inputs. |
Naming conventions
- Types name framework role:
CameraViewController and PreviewMetalView.
- Capture properties name the exact framework object:
videoDataOutput, depthDataOutput, outputSynchronizer.
- State Booleans use
is/past-participle forms: isSessionRunning, renderingEnabled.
- Methods use lifecycle/action verbs:
configureSession, sessionWasInterrupted, chooseBackground, configureMetal.
Architecture takeaways
- Make one queue-confined owner responsible for an
AVCaptureSession graph.
- Synchronize heterogeneous outputs before applying per-frame business logic.
- Keep the Metal draw loop behind a small renderer input surface.
- A large controller is understandable for a focused sample, but production growth would justify extracting capture and image-processing services.
Source map
| Source file |
Relevant symbols |
TrueDepthBackdrop/CameraViewController.swift |
capture graph, lifecycle, synchronizer delegate, Core Image blend |
TrueDepthBackdrop/PreviewMetalView.swift |
renderer inputs, transforms, Metal lifecycle |
TrueDepthBackdrop/Shaders/PassThrough.metal |
vertexPassThrough, fragmentPassThrough |
TrueDepthBackdrop/AppDelegate.swift |
application entry delegate |