Sample CodeiOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, Xcode 16.0Reviewed 2026-07-21View on Apple Developer

AVMultiCamPiP: Capturing from Multiple Cameras

At a glance

Item Summary
Purpose Preview front and back cameras simultaneously and record a Metal-composited picture-in-picture movie.
App architecture Controller-centered UIKit app that manually wires a multi-camera capture graph, delegates mixing to PiPVideoMixer, and delegates writing to MovieRecorder.
Main patterns Manual graph construction, queue confinement, compositor object, recorder facade, progressive cost reduction.
Project style Flat target with a large camera controller and small concrete preview, mixer, recorder, and utility files.

Project structure

AVMultiCamPiP/
├── CameraViewController.swift   # Session graph, UI, routing, recording coordination
├── PiPVideoMixer.swift          # Metal compositing
├── PiPMixer.metal               # Compute kernel
├── MovieRecorder.swift          # AVAssetWriter wrapper
├── PreviewView.swift            # Preview-layer-backed UIView
├── Utilities.swift
├── Base.lproj/Main.storyboard
└── AppDelegate.swift

Structure observations

  • The controller owns front/back video and microphone endpoints and their explicit connections.
  • Rendering and file writing are extracted into concrete helper objects, but there is no app-defined protocol abstraction.
  • Front/back preview views are storyboard-owned; recording uses mixed buffers rather than recording either preview layer.

Overall architecture

Reference code

AVMultiCamPiP/CameraViewController.swift:869 — frame routing and composition

func captureOutput(_ output: AVCaptureOutput,
                   didOutput sampleBuffer: CMSampleBuffer,
                   from connection: AVCaptureConnection) {
    if let videoDataOutput = output as? AVCaptureVideoDataOutput {
        processVideoSampleBuffer(sampleBuffer, fromOutput: videoDataOutput)
    } else if let audioDataOutput = output as? AVCaptureAudioDataOutput {
        processsAudioSampleBuffer(sampleBuffer, fromOutput: audioDataOutput)
    }
}

Interpretation

The multi-camera session produces independent streams. The controller classifies each frame as fullscreen or PiP, retains the latest PiP frame, and asks the mixer to composite when a fullscreen frame arrives. Only mixed video and the selected microphone stream reach the recorder.

Ownership and state

Ownership evidence

AVMultiCamPiP/CameraViewController.swift:272 and :702 — core owned objects

private let session = AVCaptureMultiCamSession()
private let dataOutputQueue = DispatchQueue(label: "data output queue")
private var movieRecorder: MovieRecorder?
private var currentPiPSampleBuffer: CMSampleBuffer?
private var videoMixer = PiPVideoMixer()
Owner Object or state Relationship Mutation authority
ViewController Session, input/output objects, connection state Creates and manually wires the capture graph Controller on sessionQueue.
ViewController PiP position, last PiP sample, mixer, recorder Retains composition/recording coordination state Controller on dataOutputQueue.
PiPVideoMixer Metal pipeline, texture cache, output pool Creates and retains GPU resources Mixer methods.
MovieRecorder Asset writer and writer inputs Creates per recording and appends samples Recorder methods called on data-output queue.
Storyboard Preview views Injects outlets; controller keeps weak layer references UIKit owns views/layers.

Class and protocol design

No app-defined protocol separates the three main concrete collaborators. The controller conforms directly to AVFoundation’s video and audio sample-buffer delegate protocols.

Type Responsibility Depends on
ViewController Build capture graph, route samples, coordinate UI/recording, reduce session cost UIKit, AVFoundation, mixer, recorder, PhotoKit
PiPVideoMixer Composite fullscreen and PiP buffers into a pooled output buffer Metal/Core Video
MovieRecorder Convert audio/video settings into a real-time AVAssetWriter session AVFoundation
PreviewView Expose an AVCaptureVideoPreviewLayer backing layer UIKit, AVFoundation

Access control

Symbol Access Verified effect Likely rationale
Session, queues, outputs, mixer, recorder, and routing state private Only the controller can reconfigure and route the graph Keeps queue-affine capture state within its coordinator.
backCameraDeviceInput private(set) plus @objc dynamic KVO can observe it, but only the controller assigns it Supports pressure observation without external mutation.
MovieRecorder.isRecording private(set) Controller can choose start/stop UI state, but recorder controls transitions Preserves writer lifecycle consistency.
Preview-layer references private weak Controller doesn’t extend layer lifetime Storyboard view remains the owner.
Main classes implicit internal Available only inside the app module No reusable framework API is declared.

Reference code

AVMultiCamPiP/MovieRecorder.swift:13 — recorder boundary

private var assetWriter: AVAssetWriter?
private var assetWriterVideoInput: AVAssetWriterInput?
private var assetWriterAudioInput: AVAssetWriterInput?
private(set) var isRecording = false

No public, open, or fileprivate declaration appears in the Swift source.

Logic ownership and placement

Logic Owning type or file Placement rationale
Front/back/microphone graph construction ViewController private setup methods Requires shared session and explicit ports/connections.
Fullscreen/PiP stream classification ViewController.processVideoSampleBuffer Depends on current UI-selected PiP device.
Pixel compositing PiPVideoMixer Encapsulates Metal resources and buffer allocation.
Movie writer lifecycle/sample append MovieRecorder Hides asset writer/input state from UI code.
Session cost degradation ViewController.checkSystemCost and helpers Reconfigures session/device formats in priority order.
Preview backing layer PreviewView Minimal UIKit/AVFoundation view concern.

Design patterns

Pattern Source evidence Purpose or tradeoff
Manual capture graph AVMultiCamPiP/CameraViewController.swift:314 and addInputWithNoConnections setup Gives exact front/back preview/data connection control required by MultiCam.
Queue confinement AVMultiCamPiP/CameraViewController.swift:276 and line 278 Separates blocking graph mutation from realtime sample work.
Compositor object AVMultiCamPiP/PiPVideoMixer.swift:11 Keeps GPU pipeline and buffer-pool details out of controller flow.
Recorder facade AVMultiCamPiP/MovieRecorder.swift:33 Presents start/stop/append operations over AVAssetWriter.
Progressive fallback chain AVMultiCamPiP/CameraViewController.swift:1004 and checkSystemCost() Reduces resolution, ports, or frame rate until capture costs fit.

Naming conventions

  • Types: responsibility nouns (PiPVideoMixer, MovieRecorder, PreviewView); the main type uses generic ViewController despite the filename’s camera context.
  • Methods: configuration names identify device side (configureBackCamera, configureFrontCamera) and processing stage (processFullScreenSampleBuffer).
  • Properties: front... and back... prefixes make parallel graph endpoints explicit.
  • State: Boolean names use is... or ...Enabled; queue names state their affinity.
  • Files: one concrete helper per file; Metal kernel separated by implementation technology.

Architecture takeaways

  • MultiCam requires explicit capture-port/connection ownership, which naturally concentrates graph coordination.
  • Realtime routing, mixing, and recording share one data-output queue, making ordering assumptions visible.
  • A small recorder wrapper prevents asset-writer lifecycle details from leaking through the controller.
  • private(set) exposes status for coordination while preserving the component that may transition it.
  • The design is concrete and sample-focused; it does not introduce protocols where only one implementation exists.

Source map

Source file Relevant symbols
AVMultiCamPiP/CameraViewController.swift Session graph, routing, recording and cost coordination
AVMultiCamPiP/PiPVideoMixer.swift PiPVideoMixer and Metal composition
AVMultiCamPiP/MovieRecorder.swift MovieRecorder, AVAssetWriter lifecycle
AVMultiCamPiP/PreviewView.swift Preview-layer-backed view
AVMultiCamPiP/Utilities.swift Orientation/session helpers