axiom-camera-capture-ref について
カメラ撮影機能を実装する開発者向けの AVFoundation API リファレンスです。セッション設定、デバイス、写真・動画出力、向きの処理、プレビュー統合をコード例と API の説明で扱います。
主な機能
- 01キャプチャセッションの設定とライフサイクル
- 02カメラデバイスの構成
- 03写真形式と撮影設定
- 04動画録画とプレビュー
- 05回転と撮影準備状態の API
活用シーン
- 01カメラキャプチャセッションの設定
- 02異なる形式での写真撮影
- 03動画の録画とプレビュー
- 04向きと撮影準備状態の処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-camera-capture-refインストール後、Claude Code で/axiom-camera-capture-refを入力して読み込まれたか確認します。
詳細
カメラキャプチャ 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 | Ultra HD 動画 |
.inputPriority | デバイスのフォーマットを使用 | カスタム構成 |
セッションの構成
// Batch configuration (atomic)
session.beginConfiguration()
defer { session.commitConfiguration() }
// Check preset support
if session.canSetSessionPreset(.hd4K3840x2160) {
session.sessionPreset = .hd4K3840x2160
}
// Add input/output
if session.canAddInput(input) {
session.addInput(input)
}
if session.canAddOutput(output) {
session.addOutput(output)
}
セッションのライフサイクル
// Start (ALWAYS on background queue)
sessionQueue.async {
session.startRunning() // Blocking call
}
// Stop
sessionQueue.async {
session.stopRunning()
}
// Check state
session.isRunning // true/false
session.isInterrupted // true during phone calls, etc.
セッションの通知
// Session started
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionDidStartRunning,
object: session, queue: .main) { _ in }
// Session stopped
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionDidStopRunning,
object: session, queue: .main) { _ in }
// Session interrupted (phone call, etc.)
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionWasInterrupted,
object: session, queue: .main) { notification in
let reason = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int
}
// Interruption ended
NotificationCenter.default.addObserver(
forName: .AVCaptureSessionInterruptionEnded,
object: session, queue: .main) { _ in }
// Runtime error
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
物理キャプチャデバイス(カメラ、マイク)を表します。
デバイスの取得
// Default back camera
AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
// Default front camera
AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
// Default microphone
AVCaptureDevice.default(for: .audio)
// Discovery session for all cameras
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() }
// Focus
if device.isFocusModeSupported(.continuousAutoFocus) {
device.focusMode = .continuousAutoFocus
}
// Exposure
if device.isExposureModeSupported(.continuousAutoExposure) {
device.exposureMode = .continuousAutoExposure
}
// Torch (flashlight)
if device.hasTorch && device.isTorchModeSupported(.on) {
device.torchMode = .on
}
// Zoom
device.videoZoomFactor = 2.0 // 2x zoom
} catch {
print("Failed to configure device: \(error)")
}
カメラの切り替え
// Switch between front and back during active session
func switchCamera() {
sessionQueue.async { [self] in
session.beginConfiguration()
defer { session.commitConfiguration() }
// Remove current camera input
if let currentInput = session.inputs.first(where: { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.video) == true }) as? AVCaptureDeviceInput {
session.removeInput(currentInput)
// Get opposite camera
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 の間に切り替えてください。
認証
// Check status
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized: break
case .notDetermined:
await AVCaptureDevice.requestAccess(for: .video)
case .denied, .restricted:
// Show settings prompt
@unknown default: break
}
AVCaptureDevice.RotationCoordinator(iOS 17+)
デバイスの向きを自動的に追跡し、回転角度を提供します。
セットアップ
// Create with device and preview layer
let coordinator = AVCaptureDevice.RotationCoordinator(
device: captureDevice,
previewLayer: previewLayer
)
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
videoRotationAngleForHorizonLevelPreview | CGFloat | プレビューレイヤーの回転角度 |
videoRotationAngleForHorizonLevelCapture | CGFloat | キャプチャ出力の回転角度 |
監視
// KVO observation for preview updates
let observation = coordinator.observe(
\.videoRotationAngleForHorizonLevelPreview,
options: [.new]
) { [weak previewLayer] coordinator, _ in
DispatchQueue.main.async {
previewLayer?.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview
}
}
// Set initial value
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()
// High resolution
photoOutput.isHighResolutionCaptureEnabled = true
// Max quality prioritization
photoOutput.maxPhotoQualityPrioritization = .quality
// Deferred processing (iOS 17+)
photoOutput.isAutoDeferredPhotoDeliveryEnabled = true
// Live Photo
photoOutput.isLivePhotoCaptureEnabled = true
// Depth
photoOutput.isDepthDataDeliveryEnabled = true
// Portrait Effects Matte
photoOutput.isPortraitEffectsMatteDeliveryEnabled = true
対応機能
// Check support before enabling
photoOutput.isHighResolutionCaptureEnabled && photoOutput.isHighResolutionCaptureSupported
photoOutput.isLivePhotoCaptureSupported
photoOutput.isDepthDataDeliverySupported
photoOutput.isPortraitEffectsMatteDeliverySupported
photoOutput.maxPhotoQualityPrioritization // .speed, .balanced, .quality
レスポンシブキャプチャ API(iOS 17+)
// Zero Shutter Lag - uses ring buffer for instant capture
photoOutput.isZeroShutterLagSupported
photoOutput.isZeroShutterLagEnabled // true by default for iOS 17+ apps
// Responsive Capture - overlapping captures
photoOutput.isResponsiveCaptureSupported
photoOutput.isResponsiveCaptureEnabled
// Fast Capture Prioritization - adapts quality for burst-like capture
photoOutput.isFastCapturePrioritizationSupported
photoOutput.isFastCapturePrioritizationEnabled
// Deferred Processing - proxy + background processing
photoOutput.isAutoDeferredPhotoDeliverySupported
photoOutput.isAutoDeferredPhotoDeliveryEnabled
AVCapturePhotoOutputReadinessCoordinator(iOS 17+)
シャッターボタンの状態を同期的に更新します。
セットアップ
let coordinator = AVCapturePhotoOutputReadinessCoordinator(photoOutput: photoOutput)
coordinator.delegate = self
キャプチャの追跡
// Call BEFORE capturePhoto()
coordinator.startTrackingCaptureRequest(using: settings)
photoOutput.capturePhoto(with: settings, delegate: self)
デリゲート
func readinessCoordinator(_ coordinator: AVCapturePhotoOutputReadinessCoordinator,
captureReadinessDidChange captureReadiness: AVCapturePhotoOutput.CaptureReadiness) {
switch captureReadiness {
case .ready: // Can capture immediately
case .notReadyMomentarily: // Brief delay, prevent double-tap
case .notReadyWaitingForCapture: // Flash firing, sensor reading
case .notReadyWaitingForProcessing: // Processing previous photo
case .sessionNotRunning: // Session stopped
@unknown default: break
}
}
AVCapturePhotoSettings
1 回の写真キャプチャの設定です。
基本設定
// Standard JPEG
var settings = AVCapturePhotoSettings()
// HEIF format
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 | 最速 | 低め | ソーシャル共有、連続撮影 |
.balanced | 中程度 | 良好 | 一般的な写真撮影 |
.quality | 最も遅い | 最高 | プロ用途、書類 |
settings.photoQualityPrioritization = .speed
フラッシュ
settings.flashMode = .auto // .off, .on, .auto
Apple ProRAW と HDR
// Check ProRAW support
if photoOutput.isAppleProRAWSupported {
photoOutput.isAppleProRAWEnabled = true
// Capture 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 configuration
settings.photoQualityPrioritization = .quality // Enables computational photography/HDR
// HDR is automatic with .balanced or .quality — no separate toggle needed
注:ProRAW には iPhone 12 Pro 以降が必要です。HDR は品質優先設定に応じて自動的に適用されます。Apple の Deep Fusion と Smart HDR は、品質設定に基づいてシステムによって制御されます。
解像度
// High resolution still image
settings.isHighResolutionPhotoEnabled = true
// Max dimensions (limit resolution)
settings.maxPhotoDimensions = CMVideoDimensions(width: 4032, height: 3024)
プレビュー/サムネイル
// Preview for immediate display
settings.previewPhotoFormat = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA
]
// Thumbnail
settings.embeddedThumbnailPhotoFormat = [
AVVideoCodecKey: AVVideoCodecType.jpeg,
AVVideoWidthKey: 160,
AVVideoHeightKey: 120
]
重要な注意事項
// Settings cannot be reused
// Each capture needs a NEW settings instance
let settings1 = AVCapturePhotoSettings() // Use once
let settings2 = AVCapturePhotoSettings() // Use for second capture
// Copy settings for similar captures
let settings2 = AVCapturePhotoSettings(from: settings1)
AVCapturePhotoCaptureDelegate
写真キャプチャイベント用のデリゲートです。
extension CameraManager: AVCapturePhotoCaptureDelegate {
// Photo capture will begin
func photoOutput(_ output: AVCapturePhotoOutput,
willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
// Show shutter animation
}
// Photo capture finished
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
guard error == nil else {
print("Capture error: \(error!)")
return
}
// Get JPEG data
if let data = photo.fileDataRepresentation() {
savePhoto(data)
}
// Or get raw pixel buffer
if let pixelBuffer = photo.pixelBuffer {
processBuffer(pixelBuffer)
}
}
// Deferred processing proxy (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)
}
// Add audio input
if let microphone = AVCaptureDevice.default(for: .audio),
let audioInput = try? AVCaptureDeviceInput(device: microphone),
session.canAddInput(audioInput) {
session.addInput(audioInput)
}
録画
// Start recording
let outputURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mov")
// Apply rotation
if let connection = movieOutput.connection(with: .video) {
connection.videoRotationAngle = rotationCoordinator.videoRotationAngleForHorizonLevelCapture
}
movieOutput.startRecording(to: outputURL, recordingDelegate: self)
// Stop recording
movieOutput.stopRecording()
// Check state
movieOutput.isRecording
movieOutput.recordedDuration
movieOutput.recordedFileSize
デリゲート
extension CameraManager: AVCaptureFileOutputRecordingDelegate {
func fileOutput(_ output: AVCaptureFileOutput,
didStartRecordingTo fileURL: URL,
from connections: [AVCaptureConnection]) {
// Recording started
}
func fileOutput(_ output: AVCaptureFileOutput,
didFinishRecordingTo outputFileURL: URL,
from connections: [AVCaptureConnection],
error: Error?) {
if let error = error {
print("Recording failed: \(error)")
return
}
// Video saved to 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 }
// Handle photo data
}
}
リソース
ドキュメント: /avfoundation/avcapturesession, /avfoundation/avcapturedevice, /avfoundation/avcapturephotosettings, /avfoundation/avcapturedevice/rotationcoordinator
スキル: axiom-camera-capture, axiom-camera-capture-diag