axiom-camera-capture-ref
AVCaptureSession、AVCapturePhotoSettings、AVCapturePhotoOutput、RotationCoordinator、photoQualityPrioritization、deferred processing、AVCaptureMovieFileOutput、session presets、capture device APIについて解説します。これらのAPIを使用して、写真や動画のキャプチャ機能を実装できます。AVCaptureSessionでセッションを管理し、AVCapturePhotoSettingsで撮影設定を指定します。AVCapturePhotoOutputは写真データを出力し、AVCaptureMovieFileOutputは動画ファイルの保存に対応しています。RotationCoordinatorでデバイスの回転に対応でき、photoQualityPrioritizationで画質優先度を調整できます。session presetsではキャプチャ品質を選択でき、capture device APIでカメラやマイクなどのデバイスを制御できます。deferred processingで後処理に対応することも可能です。
description の原文を見る
Reference — AVCaptureSession, AVCapturePhotoSettings, AVCapturePhotoOutput, RotationCoordinator, photoQualityPrioritization, deferred processing, AVCaptureMovieFileOutput, session presets, capture device APIs
SKILL.md 本文
カメラキャプチャAPI リファレンス
クイックリファレンス
// SESSION SETUP
import AVFoundation
let session = AVCaptureSession()
let sessionQueue = DispatchQueue(label: "camera.session")
sessionQueue.async {
session.beginConfiguration()
session.sessionPreset = .photo
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
let input = try? AVCaptureDeviceInput(device: camera),
session.canAddInput(input) else { return }
session.addInput(input)
let photoOutput = AVCapturePhotoOutput()
if session.canAddOutput(photoOutput) {
session.addOutput(photoOutput)
}
session.commitConfiguration()
session.startRunning()
}
// CAPTURE PHOTO
var settings = AVCapturePhotoSettings()
settings.photoQualityPrioritization = .balanced
photoOutput.capturePhoto(with: settings, delegate: self)
// ROTATION (iOS 17+)
let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: previewLayer)
previewLayer.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview
AVCaptureSession
キャプチャデータフロー用の中央コーディネーター。
セッションプリセット
| プリセット | 解像度 | 用途 |
|---|---|---|
.photo | 写真に最適 | 写真撮影 |
.high | デバイス最高品質 | ビデオ記録 |
.medium | VGA品質 | プレビュー、低ストレージ |
.low | CIF品質 | 最小ストレージ |
.hd1280x720 | 720p | HDビデオ |
.hd1920x1080 | 1080p | フルHDビデオ |
.hd4K3840x2160 | 4K | ウルトラHDビデオ |
.inputPriority | デバイスフォーマットを使用 | カスタム設定 |
セッション設定
// バッチ設定(アトミック)
session.beginConfiguration()
defer { session.commitConfiguration() }
// プリセットサポートの確認
if session.canSetSessionPreset(.hd4K3840x2160) {
session.sessionPreset = .hd4K3840x2160
}
// 入出力の追加
if session.canAddInput(input) {
session.addInput(input)
}
if session.canAddOutput(output) {
session.addOutput(output)
}
セッションライフサイクル
// 開始(常にバックグラウンドキューで実行)
sessionQueue.async {
session.startRunning() // ブロッキングコール
}
// 停止
sessionQueue.async {
session.stopRunning()
}
// 状態確認
session.isRunning // true/false
session.isInterrupted // 通話中などはtrue
セッション通知
// セッション開始
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionDidStartRunning,
object: session, queue: .main) { _ in }
// セッション停止
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionDidStopRunning,
object: session, queue: .main) { _ in }
// セッション中断(通話など)
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionWasInterrupted,
object: session, queue: .main) { notification in
let reason = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int
}
// 中断終了
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionInterruptionEnded,
object: session, queue: .main) { _ in }
// ランタイムエラー
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionRuntimeError,
object: session, queue: .main) { notification in
let error = notification.userInfo?[AVCaptureSessionErrorKey] as? Error
}
中断理由
| 理由 | 値 | 原因 |
|---|---|---|
.videoDeviceNotAvailableInBackground | 1 | アプリがバックグラウンドに移行 |
.audioDeviceInUseByAnotherClient | 2 | 別のアプリがオーディオを使用中 |
.videoDeviceInUseByAnotherClient | 3 | 別のアプリがカメラを使用中 |
.videoDeviceNotAvailableWithMultipleForegroundApps | 4 | Split View(iPad) |
.videoDeviceNotAvailableDueToSystemPressure | 5 | 温度スロットリング |
AVCaptureDevice
物理キャプチャデバイス(カメラ、マイク)を表します。
デバイス取得
// デフォルト背面カメラ
AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
// デフォルト前面カメラ
AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
// デフォルトマイク
AVCaptureDevice.default(for: .audio)
// すべてのカメラのディスカバリーセッション
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera, .builtInTelephotoCamera],
mediaType: .video,
position: .unspecified
)
let cameras = discoverySession.devices
デバイスタイプ
| タイプ | 説明 |
|---|---|
.builtInWideAngleCamera | 標準カメラ(1x) |
.builtInUltraWideCamera | 超広角カメラ(0.5x) |
.builtInTelephotoCamera | 望遠カメラ(2x、3x) |
.builtInDualCamera | 広角 + 望遠 |
.builtInDualWideCamera | 広角 + 超広角 |
.builtInTripleCamera | 広角 + 超広角 + 望遠 |
.builtInTrueDepthCamera | 前面TrueDepth(Face ID) |
.builtInLiDARDepthCamera | LiDARデプス |
デバイス設定
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
// フォーカス
if device.isFocusModeSupported(.continuousAutoFocus) {
device.focusMode = .continuousAutoFocus
}
// 露出
if device.isExposureModeSupported(.continuousAutoExposure) {
device.exposureMode = .continuousAutoExposure
}
// トーチ(懐中電灯)
if device.hasTorch && device.isTorchModeSupported(.on) {
device.torchMode = .on
}
// ズーム
device.videoZoomFactor = 2.0 // 2倍ズーム
} catch {
print("Failed to configure device: \(error)")
}
カメラ切り替え
// アクティブなセッション中に前後カメラを切り替え
func switchCamera() {
sessionQueue.async { [self] in
session.beginConfiguration()
defer { session.commitConfiguration() }
// 現在のカメラ入力を削除
if let currentInput = session.inputs.first(where: { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.video) == true }) as? AVCaptureDeviceInput {
session.removeInput(currentInput)
// 反対側のカメラを取得
let newPosition: AVCaptureDevice.Position = currentInput.device.position == .back ? .front : .back
guard let newDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: newPosition),
let newInput = try? AVCaptureDeviceInput(device: newDevice) else { return }
if session.canAddInput(newInput) {
session.addInput(newInput)
}
}
}
}
重要: セッションキュー内でbeginConfiguration/commitConfigurationの間に常に切り替えてください。
認可
// ステータス確認
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized: break
case .notDetermined:
await AVCaptureDevice.requestAccess(for: .video)
case .denied, .restricted:
// 設定プロンプト表示
@unknown default: break
}
AVCaptureDevice.RotationCoordinator(iOS 17以上)
デバイスの向きを自動的に追跡し、回転角を提供します。
セットアップ
// デバイスとプレビューレイヤーで作成
let coordinator = AVCaptureDevice.RotationCoordinator(
device: captureDevice,
previewLayer: previewLayer
)
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
videoRotationAngleForHorizonLevelPreview | CGFloat | プレビューレイヤーの回転 |
videoRotationAngleForHorizonLevelCapture | CGFloat | キャプチャ出力の回転 |
監視
// プレビュー更新のKVO監視
let observation = coordinator.observe(
\.videoRotationAngleForHorizonLevelPreview,
options: [.new]
) { [weak previewLayer] coordinator, _ in
DispatchQueue.main.async {
previewLayer?.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview
}
}
// 初期値を設定
previewLayer.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview
キャプチャへの適用
func capturePhoto() {
if let connection = photoOutput.connection(with: .video) {
connection.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelCapture
}
photoOutput.capturePhoto(with: settings, delegate: self)
}
AVCapturePhotoOutput
静止画をキャプチャする出力。
設定
let photoOutput = AVCapturePhotoOutput()
// 高解像度
photoOutput.isHighResolutionCaptureEnabled = true
// 最高品質優先度
photoOutput.maxPhotoQualityPrioritization = .quality
// 遅延処理(iOS 17以上)
photoOutput.isAutoDeferredPhotoDeliveryEnabled = true
// Live Photo
photoOutput.isLivePhotoCaptureEnabled = true
// デプス
photoOutput.isDepthDataDeliveryEnabled = true
// ポートレートエフェクトマット
photoOutput.isPortraitEffectsMatteDeliveryEnabled = true
サポート機能
// 有効化前にサポートを確認
photoOutput.isHighResolutionCaptureEnabled && photoOutput.isHighResolutionCaptureSupported
photoOutput.isLivePhotoCaptureSupported
photoOutput.isDepthDataDeliverySupported
photoOutput.isPortraitEffectsMatteDeliverySupported
photoOutput.maxPhotoQualityPrioritization // .speed, .balanced, .quality
レスポンシブキャプチャAPI(iOS 17以上)
// Zero Shutter Lag - インスタント撮影用リングバッファを使用
photoOutput.isZeroShutterLagSupported
photoOutput.isZeroShutterLagEnabled // iOS 17以上のアプリではデフォルトtrue
// レスポンシブキャプチャ - オーバーラップしたキャプチャ
photoOutput.isResponsiveCaptureSupported
photoOutput.isResponsiveCaptureEnabled
// 高速キャプチャ優先度 - バースト撮影のような品質に適応
photoOutput.isFastCapturePrioritizationSupported
photoOutput.isFastCapturePrioritizationEnabled
// 遅延処理 - プロキシ + バックグラウンド処理
photoOutput.isAutoDeferredPhotoDeliverySupported
photoOutput.isAutoDeferredPhotoDeliveryEnabled
AVCapturePhotoOutputReadinessCoordinator(iOS 17以上)
シャッターボタン状態の同期更新を提供します。
セットアップ
let coordinator = AVCapturePhotoOutputReadinessCoordinator(photoOutput: photoOutput)
coordinator.delegate = self
キャプチャ追跡
// capturePhoto()の前に呼び出す
coordinator.startTrackingCaptureRequest(using: settings)
photoOutput.capturePhoto(with: settings, delegate: self)
デリゲート
func readinessCoordinator(_ coordinator: AVCapturePhotoOutputReadinessCoordinator,
captureReadinessDidChange captureReadiness: AVCapturePhotoOutput.CaptureReadiness) {
switch captureReadiness {
case .ready: // すぐに撮影可能
case .notReadyMomentarily: // 短い遅延、ダブルタップを防ぐ
case .notReadyWaitingForCapture: // フラッシュ発火、センサー読み込み中
case .notReadyWaitingForProcessing: // 前の写真処理中
case .sessionNotRunning: // セッション停止中
@unknown default: break
}
}
AVCapturePhotoSettings
単一の写真撮影用設定。
基本設定
// 標準JPEG
var settings = AVCapturePhotoSettings()
// HEIF形式
settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])
// RAW
settings = AVCapturePhotoSettings(rawPixelFormatType: kCVPixelFormatType_14Bayer_BGGR)
// RAW + JPEG
settings = AVCapturePhotoSettings(
rawPixelFormatType: kCVPixelFormatType_14Bayer_BGGR,
processedFormat: [AVVideoCodecKey: AVVideoCodecType.jpeg]
)
品質優先度
| 値 | 速度 | 品質 | 用途 |
|---|---|---|---|
.speed | 最速 | 低い | SNS共有、高速撮影 |
.balanced | 中程度 | 良好 | 一般的な写真撮影 |
.quality | 最遅 | 最高 | プロ、ドキュメント |
settings.photoQualityPrioritization = .speed
フラッシュ
settings.flashMode = .auto // .off, .on, .auto
Apple ProRAW とHDR
// ProRAWサポート確認
if photoOutput.isAppleProRAWSupported {
photoOutput.isAppleProRAWEnabled = true
// ProRAWをキャプチャ
let query = photoOutput.isAppleProRAWEnabled
? AVCapturePhotoOutput.AppleProRAWQuery(photoOutput)
: nil
if let rawType = query?.availableRawPixelFormatTypes.first {
let settings = AVCapturePhotoSettings(
rawPixelFormatType: rawType,
processedFormat: [AVVideoCodecKey: AVVideoCodecType.hevc]
)
}
}
// HDR設定
settings.photoQualityPrioritization = .quality // 計算写真/HDRを有効化
// HDRは.balanced または .quality で自動 — 別のトグルは不要
注記: ProRAWはiPhone 12 Pro以降が必要です。HDRは品質優先度により自動です — AppleのDeep FusionとSmart HDRはシステムが品質設定に基づいて制御します。
解像度
// 高解像度静止画
settings.isHighResolutionPhotoEnabled = true
// 最大寸法(解像度制限)
settings.maxPhotoDimensions = CMVideoDimensions(width: 4032, height: 3024)
プレビュー/サムネイル
// 即座に表示するプレビュー
settings.previewPhotoFormat = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA
]
// サムネイル
settings.embeddedThumbnailPhotoFormat = [
AVVideoCodecKey: AVVideoCodecType.jpeg,
AVVideoWidthKey: 160,
AVVideoHeightKey: 120
]
重要な注記
// 設定は再利用できない
// 各撮影には新しい設定インスタンスが必要
let settings1 = AVCapturePhotoSettings() // 一度使用
let settings2 = AVCapturePhotoSettings() // 2回目の撮影用
// 類似撮影用に設定をコピー
let settings2 = AVCapturePhotoSettings(from: settings1)
AVCapturePhotoCaptureDelegate
写真撮影イベント用デリゲート。
extension CameraManager: AVCapturePhotoCaptureDelegate {
// 写真撮影が開始される
func photoOutput(_ output: AVCapturePhotoOutput,
willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
// シャッターアニメーション表示
}
// 写真撮影完了
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
guard error == nil else {
print("Capture error: \(error!)")
return
}
// JPEGデータ取得
if let data = photo.fileDataRepresentation() {
savePhoto(data)
}
// または生ピクセルバッファを取得
if let pixelBuffer = photo.pixelBuffer {
processBuffer(pixelBuffer)
}
}
// 遅延処理プロキシ(iOS 17以上)
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishCapturingDeferredPhotoProxy deferredPhotoProxy: AVCaptureDeferredPhotoProxy,
error: Error?) {
guard error == nil, let data = deferredPhotoProxy.fileDataRepresentation() else { return }
replaceThumbnailWithFinal(data)
}
}
AVCaptureMovieFileOutput
ビデオをファイルに記録する出力。
セットアップ
let movieOutput = AVCaptureMovieFileOutput()
if session.canAddOutput(movieOutput) {
session.addOutput(movieOutput)
}
// オーディオ入力を追加
if let microphone = AVCaptureDevice.default(for: .audio),
let audioInput = try? AVCaptureDeviceInput(device: microphone),
session.canAddInput(audioInput) {
session.addInput(audioInput)
}
記録
// 記録開始
let outputURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mov")
// 回転を適用
if let connection = movieOutput.connection(with: .video) {
connection.videoRotationAngle = rotationCoordinator.videoRotationAngleForHorizonLevelCapture
}
movieOutput.startRecording(to: outputURL, recordingDelegate: self)
// 記録停止
movieOutput.stopRecording()
// 状態確認
movieOutput.isRecording
movieOutput.recordedDuration
movieOutput.recordedFileSize
デリゲート
extension CameraManager: AVCaptureFileOutputRecordingDelegate {
func fileOutput(_ output: AVCaptureFileOutput,
didStartRecordingTo fileURL: URL,
from connections: [AVCaptureConnection]) {
// 記録開始
}
func fileOutput(_ output: AVCaptureFileOutput,
didFinishRecordingTo outputFileURL: URL,
from connections: [AVCaptureConnection],
error: Error?) {
if let error = error {
print("Recording failed: \(error)")
return
}
// ビデオはoutputFileURLに保存
saveToPhotoLibrary(outputFileURL)
}
}
AVCaptureVideoPreviewLayer
カメラプレビュー表示用レイヤー。
セットアップ
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)
ビデオグラビティ
| 値 | 動作 |
|---|---|
.resizeAspect | 画像全体を収める(レターボックス可能) |
.resizeAspectFill | レイヤーを埋める(端がクロップされる可能性) |
.resize | 拡大して埋める(歪む) |
SwiftUI統合
struct CameraPreview: UIViewRepresentable {
let session: AVCaptureSession
func makeUIView(context: Context) -> PreviewView {
let view = PreviewView()
view.previewLayer.session = session
view.previewLayer.videoGravity = .resizeAspectFill
return view
}
func updateUIView(_ uiView: PreviewView, context: Context) {}
class PreviewView: UIView {
override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self }
var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer }
}
}
一般的なコードパターン
完全なカメラマネージャー
import AVFoundation
@MainActor
class CameraManager: NSObject, ObservableObject {
let session = AVCaptureSession()
let photoOutput = AVCapturePhotoOutput()
private let sessionQueue = DispatchQueue(label: "camera.session")
private var rotationCoordinator: AVCaptureDevice.RotationCoordinator?
private var rotationObservation: NSKeyValueObservation?
@Published var isSessionRunning = false
func setup() async -> Bool {
guard await AVCaptureDevice.requestAccess(for: .video) else { return false }
return await withCheckedContinuation { continuation in
sessionQueue.async { [self] in
session.beginConfiguration()
defer { session.commitConfiguration() }
session.sessionPreset = .photo
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
let input = try? AVCaptureDeviceInput(device: camera),
session.canAddInput(input) else {
continuation.resume(returning: false)
return
}
session.addInput(input)
guard session.canAddOutput(photoOutput) else {
continuation.resume(returning: false)
return
}
session.addOutput(photoOutput)
photoOutput.maxPhotoQualityPrioritization = .quality
continuation.resume(returning: true)
}
}
}
func start() {
sessionQueue.async { [self] in
session.startRunning()
DispatchQueue.main.async {
self.isSessionRunning = self.session.isRunning
}
}
}
func stop() {
sessionQueue.async { [self] in
session.stopRunning()
DispatchQueue.main.async {
self.isSessionRunning = false
}
}
}
func capturePhoto() {
var settings = AVCapturePhotoSettings()
settings.photoQualityPrioritization = .balanced
if let connection = photoOutput.connection(with: .video),
let angle = rotationCoordinator?.videoRotationAngleForHorizonLevelCapture {
connection.videoRotationAngle = angle
}
photoOutput.capturePhoto(with: settings, delegate: self)
}
}
extension CameraManager: AVCapturePhotoCaptureDelegate {
nonisolated func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
guard let data = photo.fileDataRepresentation() else { return }
// 写真データを処理
}
}
リソース
ドキュメント: /avfoundation/avcapturesession, /avfoundation/avcapturedevice, /avfoundation/avcapturephotosettings, /avfoundation/avcapturedevice/rotationcoordinator
スキル: axiom-camera-capture, axiom-camera-capture-diag
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- ComeOnOliver
- ライセンス
- MIT
- 最終更新
- 2026/5/11
Source: https://github.com/ComeOnOliver/skillshub / ライセンス: MIT