ハイライト
iPadOS 17 では USB-C 経由で Apple Studio Display 内蔵カメラ、USB ウェブカメラ、HDMI スイッチャーなどの外部ビデオデバイスをアプリで利用でき、自動カメラ選択 API と
AVCaptureDeviceRotationCoordinatorにより正しいビデオ回転処理が可能になりました。
主要内容
Stage Manager により iPad は複数ディスプレイに拡張できます。iPad を Apple Studio Display に接続すると、FaceTime は自動でディスプレイのカメラを使います。その角度の方がビデオ通話に適しているためです。iPadOS 17 ではこの機能がすべてのアプリに開放されました。
(00:28)ただし外付けカメラと内蔵カメラには本質的な違いがあります。外付けカメラは iPad とは独立して動かせます。iPad を回転させても外付けカメラの向きは変わりません。そのためビデオ回転が複雑になります。
外付けカメラの検出
(04:10)外付けカメラは AVCaptureDevice.DeviceType.external で識別されます。デバイスから独立して動けるため position は .unspecified です。
内蔵カメラ向けに作られたアプリは、外付けカメラ対応にわずかな変更で済みます。ただし外付けカメラはいつでも抜かれる可能性があり、アプリは接続・切断イベントを処理する必要があります。
自動カメラ選択
(08:38)カメラ切り替えロジックを手動で扱うと複雑になります。iPadOS 17 では自動カメラ選択 API が導入されました:
userPreferredCamera: ユーザーが選んだカメラを記録。システムが起動をまたいでこの設定を保持systemPreferredCamera: ユーザー設定と現在接続されているデバイスに基づき、システムが最適なカメラを推奨
ユーザーが新しい外付けカメラを接続すると、システムは使いたいと判断し、自動でそれを優先カメラに設定します。
ビデオ回転
(17:55)AVCaptureVideoOrientation は iPadOS 17 で非推奨になりました。カメラがデバイスと一緒に回転する前提であり、外付けカメラには当てはまりません。
新しい AVCaptureDeviceRotationCoordinator は 2 つの角度を提供します:
videoRotationAngleForHorizonLevelPreview: プレビュー用。重力に対して水平な画面を維持videoRotationAngleForHorizonLevelCapture: 撮影用。写真とビデオを閲覧時に正立させる
外付けマイク
(27:38)外付けカメラ内蔵のマイクもアプリで利用できます。iPadOS 17 では USB-C 外付けマイクのサポートが改善され、Voice Isolation やエコーキャンセルの最適化チューニングが含まれます。
詳細
外付けカメラの検出
(04:10)AVCaptureDeviceDiscoverySession で外付けカメラを検出:
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.external],
mediaType: .video,
position: .unspecified
)
let externalCameras = discoverySession.devices
キーポイント:
- 外付けカメラの
deviceTypeは.external .externalUnknownは iPadOS で非推奨positionは.unspecified
自動カメラ選択
(09:11)システム推奨カメラを使用:
// 初回起動時にデフォルト設定を行う
if UserDefaults.standard.object(forKey: "hasSetInitialCamera") == nil {
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .back
)
if let backCamera = discoverySession.devices.first {
AVCaptureDevice.userPreferredCamera = backCamera
}
UserDefaults.standard.set(true, forKey: "hasSetInitialCamera")
}
// システム推奨のカメラを取得
let preferredCamera = AVCaptureDevice.systemPreferredCamera
カメラ変更の監視:
// KVO で systemPreferredCamera を監視
var observation: NSKeyValueObservation?
observation = AVCaptureDevice.observe(\.systemPreferredCamera, options: [.new]) { device, change in
guard let newCamera = change.newValue else { return }
// 新しいカメラへ切り替える。ただし録画中は中断しない
if !isRecording {
switchToCamera(newCamera)
}
}
キーポイント:
userPreferredCameraは読み書き可能。設定後システムがユーザー設定を記憶systemPreferredCameraは読み取り専用。ユーザー設定と現在利用可能なデバイスを総合- 新しいカメラ接続時、システムが自動で推奨
- 録画中はカメラ切り替えで録画を中断しない
接続・切断の処理
(06:40)外付けカメラはいつでも抜かれる可能性があるため、適切に処理する必要があります:
// 特定デバイスの接続状態を監視
var connectionObservation: NSKeyValueObservation?
connectionObservation = camera.observe(\.isConnected, options: [.new]) { device, change in
if let connected = change.newValue, !connected {
// カメラが切断されたら内蔵カメラへ切り替え
DispatchQueue.main.async {
switchToBuiltInCamera()
}
}
}
// または DiscoverySession のデバイス一覧の変化を監視
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.external, .builtInWideAngleCamera],
mediaType: .video,
position: .unspecified
)
discoverySession.addObserver(self, forKeyPath: "devices", options: [.new], context: nil)
キーポイント:
- KVO コールバックと通知はバックグラウンドキューで実行
AVCaptureSessionキューとメインスレッドへ同期が必要- 同一物理デバイスの再接続後は新しい
AVCaptureDeviceインスタンスが得られる
ビデオ回転
(19:00)AVCaptureDeviceRotationCoordinator で回転を処理:
// 回転コーディネーターを作成
var rotationCoordinator: AVCaptureDeviceRotationCoordinator?
func setupRotationCoordinator(for device: AVCaptureDevice, previewLayer: AVCaptureVideoPreviewLayer) {
rotationCoordinator = AVCaptureDeviceRotationCoordinator(
device: device,
previewLayer: previewLayer
)
// 初期プレビュー回転を設定
updatePreviewRotation()
// プレビュー回転角度の変化を監視
previewObservation = rotationCoordinator?.observe(
\.videoRotationAngleForHorizonLevelPreview,
options: [.new]
) { coordinator, change in
DispatchQueue.main.async {
self.updatePreviewRotation()
}
}
}
func updatePreviewRotation() {
guard let coordinator = rotationCoordinator else { return }
let angle = coordinator.videoRotationAngleForHorizonLevelPreview
// プレビュー接続の videoRotationAngle に適用
if let connection = previewLayer.connection,
connection.isVideoRotationAngleSupported(angle) {
connection.videoRotationAngle = angle
}
}
// 撮影時にキャプチャ回転を適用
func applyCaptureRotation(for output: AVCapturePhotoOutput) {
guard let coordinator = rotationCoordinator else { return }
let angle = coordinator.videoRotationAngleForHorizonLevelCapture
if let connection = output.connection(with: .video),
connection.isVideoRotationAngleSupported(angle) {
connection.videoRotationAngle = angle
}
}
キーポイント:
AVCaptureDeviceRotationCoordinatorはすべてのプラットフォーム(iOS、macOS、tvOS)で動作- プレビュー回転はメインキューで更新し UI と同期
- 撮影回転により写真とビデオが閲覧時に正立
- カメラ切り替え時は新しい rotation coordinator を作成
AVCaptureVideoOrientationは非推奨。新 API へ移行
カスタムプレビュー回転(VideoDataOutput 使用時)
(23:54)VideoDataOutput でカスタムプレビューを使う場合、connection 経由ではなく CALayer を回転:
// 非推奨:フレーム転送の中断を引き起こします
// videoDataOutput.connection?.videoRotationAngle = angle
// 推奨:表示レイヤーを回転
sampleBufferDisplayLayer.setAffineTransform(
CGAffineTransform(rotationAngle: angle * .pi / 180)
)
キーポイント:
AVCaptureConnection経由でVideoDataOutputを回転するとフレーム配信が中断AVSampleBufferDisplayLayerの回転の方がスムーズAVAssetWriterで録画する場合はtransformプロパティで回転を設定し、フレームごとの回転は避ける
外付けマイク
(27:38)外付けカメラ内蔵マイクの使用:
// 現在使用中のマイクを検索
let microphone = AVCaptureDevice.default(.microphone, for: .audio, position: .unspecified)
// AVAudioSession を使ってオーディオルートをより細かく制御
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playAndRecord, mode: .voiceChat)
try audioSession.setActive(true)
// 優先入力を外部マイクのデバイスに設定
if let preferredInput = audioSession.availableInputs?.first(where: { input in
input.portType == .usbAudio
}) {
try audioSession.setPreferredInput(preferredInput)
}
キーポイント:
- 新しい
.microphoneデバイスタイプが非推奨の.builtInMicrophoneに代わる - システムは最後に接続されたマイクに自動切り替え
localizedNameは現在使用中のマイクに応じて変化- Voice Isolation は外付けマイクに対応
カメラ切り替えボタンのロジック
(15:13)外付けカメラ向けの切り替えロジック:
func changeCamera() {
let currentPosition = currentDevice.position
switch currentPosition {
case .back:
// 背面カメラから外部カメラ(あれば)へ、なければ前面カメラへ切り替え
let externalSession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.external],
mediaType: .video,
position: .unspecified
)
if let externalCamera = externalSession.devices.first {
AVCaptureDevice.userPreferredCamera = externalCamera
} else {
let frontSession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .front
)
if let frontCamera = frontSession.devices.first {
AVCaptureDevice.userPreferredCamera = frontCamera
}
}
case .unspecified, .front:
// 前面/外部カメラから背面カメラへ切り替え
let backSession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .back
)
if let backCamera = backSession.devices.first {
AVCaptureDevice.userPreferredCamera = backCamera
}
default:
break
}
}
キーポイント:
- 外付けカメラの position は
.unspecified - 切り替えのたびに
userPreferredCameraを設定し、システムにユーザー設定を学習させる - アプリの種類に応じて異なる切り替え戦略を設計
重要ポイント
1. ビデオ通話アプリに外付けカメラ自動切り替えを追加
何を作るか: ユーザーがカメラ付きディスプレイに iPad を接続したとき、ビデオ通話アプリが自動でディスプレイのカメラに切り替え、より良い撮影角度を得る。
なぜ価値があるか: Apple Studio Display のカメラ位置はデスクトップビデオ通話に iPad 前面カメラより適している。自動切り替えでユーザーは手動操作不要。
始め方: systemPreferredCamera の KVO を監視し、外付けカメラ接続時に自動切り替え。通話中は現在のカメラを維持し、通話終了後に切り替え。
2. HDMI 入力対応のライブ配信アプリを構築
何を作るか: iPadOS の UVC デバイスサポートを活用し、HDMI スイッチャーのビデオ入力をライブ配信や録画で受信。
なぜ価値があるか: UVC 準拠の HDMI スイッチャーは外付けカメラとして認識される。iPad をプロ向けライブ配信のモニター兼録画デバイスにできる。
始め方: AVCaptureDevice.DiscoverySession で .external デバイスを検出し、AVCaptureSession でビデオストリームを受信。HDMI 入力はプレビューのミラー表示は不要。
3. 正しい多方向ビデオプレビューを実装
何を作るか: 旧 AVCaptureVideoOrientation を AVCaptureDeviceRotationCoordinator に置き換え、iPad 回転時も外付けカメラのプレビューが正しく表示されるようにする。
なぜ価値があるか: 旧回転 API はカメラが iPad と一緒に回転する前提で、外付けカメラには完全に誤り。新 API は重力に対して水平な画面を維持。
始め方: デバイス切り替え時に新しい AVCaptureDeviceRotationCoordinator を作成し、videoRotationAngleForHorizonLevelPreview の変化を監視して preview layer の connection に適用。
4. プロ向け撮影アプリに外付けマイク対応を追加
何を作るか: ビデオ録画中にマイク付き外付けカメラや USB マイクを接続したとき、自動で音声入力源を切り替え。
なぜ価値があるか: 外付けマイクは通常内蔵より音質が良く、Voice Isolation などの高度な機能に対応。自動切り替えでユーザーはコンテンツ制作に集中できる。
始め方: AVAudioSession の route change 通知を監視し、新しい USB 音声入力を検出したら setPreferredInput で切り替え。UI で現在使用中のマイク名を表示。
関連セッション
- Create a more responsive camera experience — iOS 17 カメラ応答性 API: 遅延処理、ゼロシャッターラグ、レスポンシブキャプチャ
- What’s new in the Photos picker — 埋め込みと連続選択に対応した Photos ピッカーの新機能
- What’s new in Background Assets — バックグラウンドアセットダウンロードでユーザーの待ち時間を削減
- Meet SwiftUI for spatial computing — 空間コンピューティングプラットフォームにおける SwiftUI の新機能
コメント
GitHub Issues · utterances