WWDC Quick Look 💓 By SwiftGGTeam
Support external cameras in your iPadOS app

Support external cameras in your iPadOS app

元の動画を見る

ハイライト

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:55AVCaptureVideoOrientation は iPadOS 17 で非推奨になりました。カメラがデバイスと一緒に回転する前提であり、外付けカメラには当てはまりません。

新しい AVCaptureDeviceRotationCoordinator は 2 つの角度を提供します:

  • videoRotationAngleForHorizonLevelPreview: プレビュー用。重力に対して水平な画面を維持
  • videoRotationAngleForHorizonLevelCapture: 撮影用。写真とビデオを閲覧時に正立させる

外付けマイク

27:38)外付けカメラ内蔵のマイクもアプリで利用できます。iPadOS 17 では USB-C 外付けマイクのサポートが改善され、Voice Isolation やエコーキャンセルの最適化チューニングが含まれます。

詳細

外付けカメラの検出

04:10AVCaptureDeviceDiscoverySession で外付けカメラを検出:

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:00AVCaptureDeviceRotationCoordinator で回転を処理:

// 回転コーディネーターを作成
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:54VideoDataOutput でカスタムプレビューを使う場合、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. 正しい多方向ビデオプレビューを実装

何を作るか: 旧 AVCaptureVideoOrientationAVCaptureDeviceRotationCoordinator に置き換え、iPad 回転時も外付けカメラのプレビューが正しく表示されるようにする。

なぜ価値があるか: 旧回転 API はカメラが iPad と一緒に回転する前提で、外付けカメラには完全に誤り。新 API は重力に対して水平な画面を維持。

始め方: デバイス切り替え時に新しい AVCaptureDeviceRotationCoordinator を作成し、videoRotationAngleForHorizonLevelPreview の変化を監視して preview layer の connection に適用。

4. プロ向け撮影アプリに外付けマイク対応を追加

何を作るか: ビデオ録画中にマイク付き外付けカメラや USB マイクを接続したとき、自動で音声入力源を切り替え。

なぜ価値があるか: 外付けマイクは通常内蔵より音質が良く、Voice Isolation などの高度な機能に対応。自動切り替えでユーザーはコンテンツ制作に集中できる。

始め方: AVAudioSession の route change 通知を監視し、新しい USB 音声入力を検出したら setPreferredInput で切り替え。UI で現在使用中のマイク名を表示。

関連セッション

コメント

GitHub Issues · utterances