AVCaptureSession
- AVFoundation
- AVCaptureSession
AVCaptureSession에 대해 학습하기 전, AVFoundation에 대해 짚고 넘어가보자.
AVFoundation이란?

AVFoundation | Apple Developer Documentation
AVFoundation은 Apple의 iOS나 macOS, tvOS 플랫폼 등에서 시청각 미디어를 캡쳐하거나 재생, 편집, 처리하는 프레임워크이다!
우리가 흔히 사용하는 카메라 촬영(입력/출력)부터 비디오, 오디오 파일 관리, 실시간 스트리밍까지 모두 포괄한다.
AVFoundation으로 활용할 수 있는 영역은 다음과 같다.
- 미디어 관리
→ AVAsset 클래스를 활용해 파일이나 입출력 스트림, 네트워크 미디어를 추상화하여 독립적으로 로드하고 메타데이터를 처리할 수 있다.
- 캡처 및 입력 관리
→ AVCaptureSession을 통해 카메라나 마이크와 같은 입, 출력 하드웨어의 실시간 데이터 흐름을 구축할 수 있다.
- 재생 및 출력
→ AVPlayer를 이용하여 동영상과 오디오 재생, AVAudioPlayer 로 간단한 오디오 처리나 AVKit을 통해 UI 구현을 할 수 있다.
AVFoundation의 아키텍처
AVFoundation은 Core Audio, Core Media, Core Animation 위에 구축되어 코덱과 컨테이너 같이 저레벨 구성 요소를 감추고 고레벨 API를 제공한다.
예를 들어서.. 카메라 앱에서는 AVCapture로 입/출력 파이프라인 구축을, 동영상 플레이어에서는 AVPlayer로 동영상 재생 제어를 구현할 수 있다.
아래 이미지는 iOS 에서의 AVFoundation 레이어를 뜻한다.

아래 이미지는 MacOS(OX X) 에서의 AVFoundation 레이어를 뜻한다. (iOS의 AVFoundation과 약간 차이가 있는 것 같다.)

카메라 제어 및 관리는 AVFoundation 하위에 존재하는 AVCapture 계열을 활용하여 구현할 수 있다!
AVAsset, AVKit, AVPlayer, AVAudioSession 등은 넘어간다.
위 내용들이 정말 잘 정리되어있는 문서 : https://ios-development.tistory.com/927
AVCapture 개념
AVCapture는 AVFoundation 안에서 실시간 오디오, 비디오 캡처를 위한 계층을 통칭해서 부르는 개념이라고 보면 된다.
카메라와 마이크에서 들어오는 스트림을 입력 -> 처리 -> 출력 단계로 흘려보내는 파이프라인을 구축하고 이 과정에서 CPU, GPU, 메모리 등을 사용한다.
주요 구성 요소는 다음과 같다.
AVCaptureSession
- AVCapture 파이프라인의 핵심이자 허브이다.
- 어떤 입력을 어떤 출력으로 연결할지 정의하고
start와stop을 관리한다.
AVCaptureDevice / AVCaptureDeviceInput
- 실제 하드웨어(카메라, 마이크)를 선택하고 이를
Session의input으로 연결한다.
AVCaptureOutput계열 → 캡처된 데이터를 어떻게 활용할 것인지 결정한다.
AVCapturePhotoOutput: 사진 캡처AVCaptureMovieFileOutput: 동영상 파일AVCaptureVideoDataOutput,AVCaptureAudioDataOutput: 프레임, 오디오 샘플을 콜백으로 직접 받아 처리한다.
AVCaptureConnection
- 입력, 출력 사이의 연결 관리, 방향이나 안정화 등의 설정을 진행한다.
AVCaptureVideoPreviewLayer
Session에 연결하여 실시간 프리뷰를 화면에 렌더링할 수 있다.
🤔 그래서 이것들로 무엇을 할 수 있을까?
1️⃣ 카메라 앱 구현2️⃣ 실시간 비디오, 오디오 처리
3️⃣ 고급
1️⃣ 카메라 앱 구현
- 전면/후면 카메라 선택, 포커스/노출 조정, 플래시/토치, 사진 촬영, 동영상 녹화 등 기본 카메라 기능을 모두 구현할 수 있다.
2️⃣ 실시간 비디오, 오디오 처리
VideoDataOutput으로CMSampleBuffer를 받아서 필터링, 얼굴 인식, 머신러닝 추론 등 커스텀 파이프라인을 구축할 수 있다.AudioDataOutput으로 실시간 오디오 분석, 시각화, 필터 등을 적용할 수 있다.
3️⃣ 고급
- 멀티 카메라(듀얼이나 울트라와이드 같은?)를 결합하여 심도 효과, 고해상도 사진, HDR과 포트레이트 효과 등도
AVCapture계층 위에서 제공되는 고수준 API를 통해 구현할 수 있다.
🤔 CPU, GPU, 메모리와는 무슨 관계일까?
1️⃣ CPU와의 관계2️⃣ GPU와의 관계
3️⃣ 메모리와의 관계
1️⃣ CPU와의 관계
Session구성 변경(입, 출력 추가 및 제거), 포맷 선택, 포커스 및 노출 제어, 제스처 처리, 파일 입출력 등제어 로직과 일반적인 앱 로직은 주로CPU가 처리한다.VideoDataOutput콜백에서 ⭐CMSampleBuffer를 직접 만지기에 여기서 포맷 변환이나 인코딩, ML 추론 등을 시도하면 CPU 부담이 커지므로 최적화를 반드시 고려하자.- 프레임 처리 비용이 크면 클수록 프레임 드랍 현상과 발열, UI 블로킹, 배터리 광탈 등의 현상이 발생하므로 가능한
Metal이나Core Image과 같이 GPU나 전용 하드웨어로 오프로드 시키는 것이 좋다고 한다.(살짝 이해가 안간다..)
2️⃣ GPU와의 관계
- ⭐ 카메라에서 나온 윈시 프레임을 Image Signal Processor가 1차적으로 처리하고 이후 프리뷰 렌더링과 이미지 필터링 작업은 GPU가 수행한다.
AVCaptureVideoPreviewLayer는 내부적으로Core Animation과GPU를 통해 프레임을 화면에 뿌린다.Metal이나Core Image와 함께 사용하면CMSampleBuffer→CVPixelBuffer→ 텍스처로 이어지는 나만의 GPU 파이프라인을 구성할 수 있어서 여기서 커스텀할 수 있다!
3️⃣ 메모리와의 관계
- 카메라 스트림은 ⭐ 초당 수십 장의 고해상도 프레임(CVPixelBuffer)을 다루기 때문에 버퍼 수와 해상도에 비래하여 메모리 사용량이 커지므로 유의하자.
- 고해상도 / 4K / 60FPS로 올릴 수록 버퍼당 크기와 초당 버퍼 수가 계속 우상향하므로 메모리와 대역폭 부담이 커지게 되니 유의하자.
- ⭐ 불필요한 버퍼 복사와 보관을 줄이고 필요한 경우엔 GPU와 CVMetalTextureCache 등 공유 메모리를 사용하여
copy-less파이프라인을 구성하자.
→ https://developer.apple.com/documentation/corevideo/cvmetaltexturecache-q3j
AVCapture 구현 예시
1. AVCaptureSession
💡 AVCaptureSession은 입력 장치에서 캡처 출력으로 데이터 흐름을 조정하고 캡처 동작을 구성하는 객체이다. 모든 AVCapture 파이프라인의 중심에 위치하며
어떤 입력을어떤 출력으로 연결할지 정의한다.
주요 속성 및 메서드 정리
sessionPreset : 캡처 품질과 해상도를 설정한다.
let session = AVCaptureSession()
session.sessionPreset = .hd1920x1080
// 주요 preset 옵션
// .photo - 최고 해상도 사진 품질
// .high - 고품질 비디오 출력
// .medium - 중간 품질
// .low - 낮은 품질
// .hd1280x720 - 720p HD
// .hd1920x1080 - 1080p Full HD
// .hd4K3840x2160 - 4K
// .vga640x480 - VGA 해상도
단, 위
참고단락에서 정리한 것 처럼 높은 해상도일수록 CPU와 GPU의 부하와 메모리 사용량이 커지므로 요구사항에 맞는 적절한 preset을 선택하자.
생명주기 메서드
// 세션 시작 (blocking 메서드이므로 백그라운드 큐에서 호출하는 게 좋다)
session.startRunning()
// 세션 중지
session.stopRunning()
// 세션 실행 여부 확인
if session.isRunning {
print("세션이 실행중입니다.")
}
Configuration 메서드
beginConfiguration__,commitConfiguration은 실행 중인 캡처 세션의 설정 변경을중간 상태 없이 한 번에 적용하기 위한안전장치이다.
// 올바른 사용법
session.beginConfiguration()
// 여러 설정 변경을 원자적으로 처리
if session.canAddInput(videoInput) {
session.addInput(videoInput)
}
if session.canAddOutput(photoOutput) {
session.addOutput(photoOutput)
}
session.commitConfiguration()
// 잘못된 사용법 - startRunning()을 configuration 사이에 호출하지 말자.
session.beginConfiguration()
session.startRunning() // 에러 발생 뚜둥
session.commitConfiguration()
즉, 잘못된 사용법은 더 쉽게 말하면 아래와 같다.
session.beginConfiguration()
// 이 사이의 변경은 즉시 적용되지 않음
session.removeInput(oldInput)
session.addInput(newInput)
session.addOutput(videoOutput)
session.sessionPreset = .high
session.commitConfiguration() // 여기서 한 번에 적용된다.
AVCaptureSession은 보통 실행 중인 상태에서 설정을 바꾼다.
Session이 running 중일 때 입력 디바이스를 바꾸거나 출력을 추가하거나 해상도 프리셋을 변경하는 설정을 하나씩 바로 적용하면 문제가 생길 수 있다.
session.addInput(cameraInput) // 1단계
session.addOutput(videoOutput) // 2단계
session.sessionPreset = .high // 3단계
위처럼 구성하면 실행 중인 세선은 1단계 반영 완료, 2단계 반영 완료 … 이런 중간 상태를 가지기 떄문에 크래시나 프레임 드롭 등의 현상이 발생할 수 있으므로 beginConfiguration, commitConfiguration 을 잘 활용하자.
⭐ 단, 세션 최초 설정이거나 단일 설정만 변경하고 Session이 멈춰있을 때는 상관없다.
스레드 안정성
Session 관련 모든 작업은 blocking이므로 별도의 직렬 큐(serial queue)에서 처리하는 것이 권장되므로 유의하자.

private let sessionQueue = DispatchQueue(label: "session.queue") // 이렇게!
sessionQueue.async { [weak self] in
guard let self else { return }
self.session.startRunning()
}
예시 코드
import AVFoundation
class CameraManager {
private let session = AVCaptureSession()
private let sessionQueue = DispatchQueue(label: "session.queue")
func setupSession() {
sessionQueue.async { [weak self] in
guard let self = self else { return }
self.session.beginConfiguration()
// MARK: - Preset 설정
if self.session.canSetSessionPreset(.hd1920x1080) {
self.session.sessionPreset = .hd1920x1080
}
// MARK: - Input 및 Output 추가는 이후 섹션에서 진행한다.
self.session.commitConfiguration()
}
}
func startSession() {
sessionQueue.async { [weak self] in
self?.session.startRunning()
}
}
func stopSession() {
sessionQueue.async { [weak self] in
self?.session.stopRunning()
}
}
}
2. AVCaptureDevice/Input
💡 AVCaptureDevice는 카메라나 마이크와 같은 하드웨어 또는 가상 캡처 장치를 나타내는 객체이다.
카메라 디바이스 찾기 (iOS 17+)
Discovery Session사용을 권장한다.
import AVFoundation
// MARK: - 후면 카메라 찾기
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [
.builtInWideAngleCamera, // 일반 광각 카메라
.builtInUltraWideCamera, // 초광각 카메라 (지원 기기만)
.builtInTelephotoCamera, // 망원 카메라 (지원 기기만)
.builtInDualCamera, // 듀얼 카메라 시스템
.builtInTripleCamera, // 트리플 카메라 시스템
.builtInTrueDepthCamera // True Depth 카메라 (전면)
],
mediaType: .video,
position: .back // 후면
)
guard let backCamera = discoverySession.devices.first else {
print("후면 카메라를 찾을 수 없습니다")
return
}
// MARK: - 전면 카메라 찾기
let frontDiscoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .front // 전면
)
guard let frontCamera = frontDiscoverySession.devices.first else {
print("전면 카메라를 찾을 수 없습니다")
return
}
간단하게 기본 카메라를 사용하는 방법은?
// 기본 후면 카메라
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video,
position: .back) else {
print("카메라를 사용할 수 없습니다")
return
}
AVCaptureDeviceInput 생성
do {
let videoInput = try AVCaptureDeviceInput(device: backCamera)
// Session에 추가하기 전 확인한다.
if session.canAddInput(videoInput) {
session.addInput(videoInput)
} else {
print("비디오 입력을 추가할 수 없습니다")
}
} catch {
print("카메라 입력 생성 실패: \(error.localizedDescription)")
}
카메라 전환 기능 추가하기
class CameraManager {
private let session = AVCaptureSession()
private var currentCamera: AVCaptureDevice?
private var currentInput: AVCaptureDeviceInput?
func switchCamera() {
sessionQueue.async { [weak self] in
guard let self else { return }
self.session.beginConfiguration()
// 기존 입력 제거
if let currentInput = self.currentInput {
self.session.removeInput(currentInput)
}
// 새 카메라 선택
let newPosition: AVCaptureDevice.Position =
self.currentCamera?.position == .back ? .front : .back
guard let newCamera = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: newPosition
) else {
self.session.commitConfiguration()
return
}
// 새 입력 추가
do {
let newInput = try AVCaptureDeviceInput(device: newCamera)
if self.session.canAddInput(newInput) {
self.session.addInput(newInput)
self.currentCamera = newCamera
self.currentInput = newInput
}
} catch {
print("카메라 전환 실패: \(error)")
}
self.session.commitConfiguration()
}
}
}
3. AVCaptureOutput
AVCapturePhotoOutput - 사진 캡처
정지된 이미지, Live Photos 및 기타 사진 타입을들 위한 캡처 출력이다.
let photoOutput = AVCapturePhotoOutput()
// Session에 추가
if session.canAddOutput(photoOutput) {
session.addOutput(photoOutput)
}
// 사진 촬영
func capturePhoto() {
let settings = AVCapturePhotoSettings()
// Flash 설정 (선택적)
if photoOutput.supportedFlashModes.contains(.auto) {
settings.flashMode = .auto
}
// 고해상도 캡처 활성화
settings.isHighResolutionPhotoEnabled = true
photoOutput.capturePhoto(with: settings, delegate: self)
}
// Delegate 구현
extension CameraManager: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
guard error == nil else {
print("사진 캡처 실패: \(error!)")
return
}
guard let imageData = photo.fileDataRepresentation() else {
print("이미지 데이터 변환 실패")
return
}
// UIImage로 변환
if let image = UIImage(data: imageData) {
// 이미지 저장 또는 처리
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
}
AVCaptureMovieFileOutput - 동영상 녹화
QuickTime동영상 파일로 비디오와 오디오를 녹화하는 캡처 출력
let movieOutput = AVCaptureMovieFileOutput()
// Session에 추가
if session.canAddOutput(movieOutput) {
session.addOutput(movieOutput)
}
// 녹화 시작
func startRecording() {
guard !movieOutput.isRecording else { return }
let outputPath = NSTemporaryDirectory() + "output.mov"
let outputURL = URL(fileURLWithPath: outputPath)
// 기존 파일 삭제
if FileManager.default.fileExists(atPath: outputPath) {
try? FileManager.default.removeItem(atPath: outputPath)
}
movieOutput.startRecording(to: outputURL, recordingDelegate: self)
}
// 녹화 중지
func stopRecording() {
if movieOutput.isRecording {
movieOutput.stopRecording()
}
}
// Delegate 구현
extension CameraManager: AVCaptureFileOutputRecordingDelegate {
func fileOutput(_ output: AVCaptureFileOutput,
didFinishRecordingTo outputFileURL: URL,
from connections: [AVCaptureConnection],
error: Error?) {
if let error = error {
print("녹화 실패: \(error)")
return
}
// 동영상 저장
UISaveVideoAtPathToSavedPhotosAlbum(outputFileURL.path,
nil, nil, nil)
}
}
AVCaptureVideoDataOutput - 실시간 프레임 처리
프레임별로 데이터를 받아서 필터링, 객체 인식 등 커스텀 처리를 할 수 있다.
let videoDataOutput = AVCaptureVideoDataOutput()
// 픽셀 포맷 설정
videoDataOutput.videoSettings = [
kCVPixelBufferPixelFormatTypeKey as String:
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
]
// 늦게 도착한 프레임 드랍
videoDataOutput.alwaysDiscardsLateVideoFrames = true
// Delegate 큐 설정
let videoQueue = DispatchQueue(label: "video.queue",
qos: .userInteractive)
videoDataOutput.setSampleBufferDelegate(self, queue: videoQueue)
// Session에 추가
if session.canAddOutput(videoDataOutput) {
session.addOutput(videoDataOutput)
}
// Delegate 구현
extension CameraManager: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) {
// CMSampleBuffer 처리
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
// 이미지 처리, ML 모델 추론 등
// 예: Core Image 필터 적용
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
// ... 필터링 로직
}
func captureOutput(_ output: AVCaptureOutput,
didDrop sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) {
print("프레임 드랍 발생")
}
}
AVCaptureConnection - 입출력 연결 관리
AVCaptureConnection은 입력과 출력 사이의 연결을 관리하고 방향, 안정화 등을 설정한다.
// Output에서 Connection 가져오기
if let connection = videoDataOutput.connection(with: .video) {
// 비디오 방향 설정
if connection.isVideoOrientationSupported {
connection.videoOrientation = .portrait
}
// 비디오 미러링 (전면 카메라용)
if connection.isVideoMirroringSupported {
connection.isVideoMirrored = true
}
// 비디오 안정화 (지원되는 경우)
if connection.isVideoStabilizationSupported {
connection.preferredVideoStabilizationMode = .auto
}
}
AVCaptureVideoPreviewLayer - 실시간 프리뷰
카메라 장치의 비디오를 표시하는 Core Animation 레이어
이때, UIKit 기반 UIView이기 때문에 SwiftUI에서 사용하려면 UIViewRepresentable로 감싸야한다.
UIKit 에서 사용할 경우
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
// View에 추가
view.layer.addSublayer(previewLayer)
// 방향 설정
if let connection = previewLayer.connection {
connection.videoOrientation = .portrait
}
SwiftUI 에서 사용할 경우
import SwiftUI
import AVFoundation
struct CameraPreview: UIViewRepresentable {
let session: AVCaptureSession
func makeUIView(context: Context) -> VideoPreviewView {
let view = VideoPreviewView()
view.backgroundColor = .black
view.videoPreviewLayer.session = session
view.videoPreviewLayer.videoGravity = .resizeAspectFill
return view
}
func updateUIView(_ uiView: VideoPreviewView, context: Context) {
// 업데이트 로직 (필요시)
}
}
class VideoPreviewView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
// 사용 예시
struct ContentView: View {
@StateObject private var camera = CameraManager()
var body: some View {
CameraPreview(session: camera.session)
.onAppear {
camera.startSession()
}
.onDisappear {
camera.stopSession()
}
}
}
AVCapture를 사용할 때 유의사항
1. 권한 처리
카메라와 마이크 사용 목적을 _
Info.plist_에 반드시 명시하자.
<key>NSCameraUsageDescription</key>
<string>사진과 비디오를 촬영하기 위해 카메라 접근이 필요합니다.</string>
<key>NSMicrophoneUsageDescription</key>
<string>비디오와 함께 오디오를 녹음하기 위해 마이크 접근이 필요합니다.</string>
또한, AVCaptureSession 을 사용하기 전에는 항상 authorizationStatus() 를 통해 권한 여부를 확인해야한다.
let status = AVCaptureDevice.authorizationStatus(for: .video)
var isAuthorized = status == .authorized
// 아직 권한을 요청하지 않은 경우
if status == .notDetermined {
isAuthorized = await AVCaptureDevice.requestAccess(for: .video)
}
좀 더 응용하면 switch문을 통해 권한 상태별 처리가 가능하다.
func handleCameraPermission() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
// 권한이 이미 허용됨 -> 카메라 사용 가능
setupCamera()
case .notDetermined:
// 권한을 아직 요청하지 않음 -> 요청하기
Task {
let granted = await AVCaptureDevice.requestAccess(for: .video)
if granted {
setupCamera()
}
}
case .denied:
// 사용자가 권한을 거부함 -> 설정으로 안내
showSettingsAlert()
case .restricted:
// 권한이 제한되었을 경우 -> 이런 경우가 있나...? 🧐
showRestrictedAlert()
@unknown default:
break
}
}
2. Session 중단 상황 처리
Session은 아래와 같은 상황에서 중단될 수 있다.
- 전화 수신
- 다른 앱이 오디오 세션을 먹고 있을 때
- 앱이 백그라운드로 전환되었을 때
- 멀티태스킹
각 모든 경우를 반드시 처리해줘야 안정적인 앱이 될 수 있다.
참고 문서
[iOS - swift] 1. AVFoundation 개념 - 구조, AVAsset, AVKit
[AVFoundation] AVFoundation에서 자주 쓰이는 객체들에 관하여
[iOS] AVFoundation, AVAudioPlayer, Timer
[iOS] AVFoundation & Media Playback Programming Guide
AVCaptureSession | Apple Developer Documentation
Setting up a capture session | Apple Developer Documentation
Requesting authorization to capture and save media | Apple Developer Documentation
AVCapturePhotoOutput | Apple Developer Documentation
AVCaptureDevice.DiscoverySession | Apple Developer Documentation