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

AVCamFilter: Applying filters to a capture stream

At a glance

Item Summary
Purpose Apply Core Image or Metal rose filters and optional depth visualization to live preview and captured photos.
App architecture Controller-centered UIKit capture app with pluggable renderer objects and a custom Metal preview.
Main patterns Renderer strategy protocol, delegate-driven media pipeline, queue confinement, prepare/render/reset lifecycle.
Project style Flat camera target with the main controller, renderer classes, a Metal preview, shaders, and a small Objective-C bridge utility.

Project structure

AVCamFilter/
├── CameraViewController.swift       # Capture graph, UI state, frame/photo coordination
├── FilterRenderer.swift             # Renderer capability and buffer-pool helpers
├── RosyMetalRenderer.swift
├── RosyCIRenderer.swift
├── DepthToGrayscaleConverter.swift
├── VideoMixer.swift
├── PreviewMetalView.swift
├── Shaders/                         # Metal kernels
├── minMaxFromBuffer.{h,m}           # Bridged depth utility
├── Base.lproj/Main.storyboard
└── AppDelegate.swift

Structure observations

  • Capture and screen coordination remain in a large CameraViewController; GPU/Core Image algorithms are separate concrete objects.
  • Each renderer owns its own buffer pool, format descriptions, and framework resources.
  • Live and photo paths use separate renderer/mixer instances, avoiding shared mutable GPU preparation state across those paths.

Overall architecture

Reference code

AVCamFilter/CameraViewController.swift:1040 — live-frame pipeline

var finalVideoPixelBuffer = videoPixelBuffer
if let filter = videoFilter {
    filter.prepare(with: formatDescription, outputRetainedBufferCountHint: 3)
    finalVideoPixelBuffer = filter.render(pixelBuffer: finalVideoPixelBuffer)!
}
// The optional depth mixer updates finalVideoPixelBuffer.
previewView.pixelBuffer = finalVideoPixelBuffer

Interpretation

AVFoundation delivers frames to the controller, which chooses and sequences renderer objects before handing the final buffer to a custom Metal view. The forced unwrap above abbreviates the source’s guarded result; the source handles failure before assignment.

Ownership and state

Ownership evidence

AVCamFilter/CameraViewController.swift:62 — retained pipeline objects

private let session = AVCaptureSession()
private let videoDataOutput = AVCaptureVideoDataOutput()
private let depthDataOutput = AVCaptureDepthDataOutput()
private let photoOutput = AVCapturePhotoOutput()
private let filterRenderers: [FilterRenderer] = [RosyMetalRenderer(), RosyCIRenderer()]
private let videoDepthMixer = VideoMixer()
private let videoDepthConverter = DepthToGrayscaleConverter()
Owner Object or state Relationship Mutation authority
CameraViewController Session, outputs, queues, active renderer selection Creates and retains the entire capture/processing graph Controller, dispatched to the appropriate queue.
Each renderer Buffer pool, format descriptions, Metal/Core Image resources Creates resources during prepare, releases during reset Concrete renderer only.
PreviewMetalView Current buffer and Metal presentation resources Receives buffers; retains synchronized internal copies View, guarded by syncQueue.
Storyboard PreviewMetalView and controls Injects outlets into controller UIKit lifecycle owns view instances.

Class and protocol design

Reference code

AVCamFilter/FilterRenderer.swift:10 — renderer capability

protocol FilterRenderer: AnyObject {
    var isPrepared: Bool { get }
    func prepare(with inputFormatDescription: CMFormatDescription,
                 outputRetainedBufferCountHint: Int)
    func reset()
    func render(pixelBuffer: CVPixelBuffer) -> CVPixelBuffer?
}
Type Responsibility Depends on
CameraViewController Capture setup, feature selection, delegate processing, save flow AVFoundation, renderer/mixer objects, UIKit, PhotoKit
FilterRenderer Normalize lifecycle and pixel-buffer transformation Core Media/Core Video
RosyMetalRenderer Compute-shader rose filter Metal
RosyCIRenderer Core Image rose filter Core Image
DepthToGrayscaleConverter Convert depth to a grayscale pixel buffer Metal and Objective-C min/max helper
VideoMixer Blend video and processed depth buffers Metal
PreviewMetalView Present final buffers with rotation/mirroring MetalKit

Access control

Symbol Access Verified effect Likely rationale
Controller outlets, session, outputs, queues, and renderer instances private Other files cannot rewire the capture or processing graph Centralizes order-sensitive mutation in the controller.
Renderer input/output format descriptions private(set) Pipeline code may inspect formats but only the renderer updates them Keeps prepare/reset invariants local.
Renderer GPU resources and buffer pools private Only the concrete algorithm accesses its resources Prevents invalid cross-renderer sharing.
FilterRenderer and concrete types implicit internal Usable within the app module only The protocol supports app-local strategies, not a framework API.

Reference code

AVCamFilter/RosyMetalRenderer.swift:18 — externally readable renderer state

private(set) var inputFormatDescription: CMFormatDescription?
private(set) var outputFormatDescription: CMFormatDescription?
private var outputPixelBufferPool: CVPixelBufferPool?
private var computePipelineState: MTLComputePipelineState?

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

Logic ownership and placement

Logic Owning type or file Placement rationale
Session/device lifecycle and feature toggles CameraViewController Coordinates storyboard controls with capture reconfiguration.
Live/video sequencing CameraViewController.processVideo Chooses active strategy and optional depth mix for each frame.
Photo processing/save Photo delegate in CameraViewController Coordinates captured photo, renderer instances, depth, JPEG, and PhotoKit.
Filter algorithms/resources Renderer types Swappable implementations behind one protocol.
Display transforms and draw loop PreviewMetalView Presentation-specific Metal state stays inside the view.
Shared pixel-buffer allocation FilterRenderer.swift free functions Reused by multiple concrete renderers.

Design patterns

Pattern Source evidence Purpose or tradeoff
Strategy AVCamFilter/FilterRenderer.swift:10 and renderer arrays at AVCamFilter/CameraViewController.swift:81 Switches Core Image and Metal implementations through one contract.
Explicit renderer lifecycle AVCamFilter/FilterRenderer.swift:16 Makes expensive preparation and reset visible; callers must uphold ordering.
Delegate-driven pipeline AVCamFilter/CameraViewController.swift:14 and output callbacks at line 1026 Accepts video, depth, synchronized, and photo deliveries.
Queue confinement AVCamFilter/CameraViewController.swift:67, line 71, and line 105 Separates session mutation, realtime data, and photo processing work.
Adapter/view bridge AVCamFilter/PreviewMetalView.swift:12 Replaces the standard preview layer so the app can transform frames before display.

Naming conventions

  • Types: operation-plus-role names (DepthToGrayscaleConverter, RosyMetalRenderer, VideoMixer).
  • Protocol: capability role FilterRenderer; concrete types name technology where alternatives matter (Metal, CI).
  • Methods: lifecycle verbs (prepare, render, reset) and event processing (processVideo, processDepth).
  • State: ...Enabled, ...On, and isPrepared make Boolean scope explicit.
  • Files: one renderer or visual component per file; shader filenames match their effects.

Architecture takeaways

  • Keep capture coordination concrete while moving expensive transformation algorithms behind a small renderer protocol.
  • Separate live and photo renderer instances when preparation state and buffer pools must not race.
  • Use a custom render view when frames need modification before presentation.
  • private(set) is useful for renderer lifecycle state that callers inspect but must not mutate.
  • The controller remains large; the source optimizes sample visibility over a fully layered application architecture.

Source map

Source file Relevant symbols
AVCamFilter/CameraViewController.swift Capture ownership, delegates, live/photo pipelines
AVCamFilter/FilterRenderer.swift FilterRenderer, allocation helpers
AVCamFilter/RosyMetalRenderer.swift Metal strategy
AVCamFilter/RosyCIRenderer.swift Core Image strategy
AVCamFilter/DepthToGrayscaleConverter.swift Depth conversion strategy
AVCamFilter/VideoMixer.swift Depth/video composition
AVCamFilter/PreviewMetalView.swift Metal preview and transforms