iPad Proのセンターフレーム (Center Stage)のAPIを使う

6 min read読了の目安(約5900字

センターフレーム (Center Stage) とは

インカメラで人が映っている時に広角カメラの画角を自動で調整して人をフレーム内に収めてくれる機能です。
iOS 14.5以上で使える機能で、現時点 (2021/5/28) ではM1 iPadでのみ使えます。

英語では "Center Stage" ですが、日本語では「センターフレーム」と呼ぶようです。

https://twitter.com/tanakasan2525/status/1398086776477024260

APIを使ってみる

まだほとんどドキュメントがないですが、AVFoundationの普通の使い方をしつつ、フラグを設定するだけで機能が使えます。

そのフラグは AVCaptureDevice.centerStageControlMode です。

このフラグを設定すると設定アプリの権限ページにセンターフレームのOn/Offを切り替えるスイッチが追加されます。

このスイッチは他のカメラなどの許諾とは異なり、アプリ側からONに切り替えたり、設定アプリからOffにできないようすることもできます。

// 設定アプリでの設定でセンターフレームを使うかを決定する
AVCaptureDevice.centerStageControlMode = .user

// 起動時は設定アプリでの設定に従い、アプリ側からセンターフレームのOn/Offを切り替えると
// 設定アプリの状態も同期される
AVCaptureDevice.centerStageControlMode = .cooperative

// 設定アプリからは変更できないようにし、アプリ内の設定に従わせる
AVCaptureDevice.centerStageControlMode = .app

cooperativeappに設定した場合は下記のフラグでセンターフレームの有効性を切り替えられます。

AVCaptureDevice.isCenterStageEnabled = true

https://developer.apple.com/documentation/avfoundation/avcapturedevice/3738419-iscenterstageenabled

appに設定した場合、設定アプリのスイッチがdisabledになり、切り替えられなくなります。

ソースコード

センターフレームの機能を試すコードはこちらです。

import AVFoundation
import SwiftUI

final class VideoCaptureViewModel: ObservableObject {
    private lazy var captureSession = AVCaptureSession()
    lazy var previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)

    override init() {
        super.init()
        configure()
    }
    
    private func configure() {
        captureSession.sessionPreset = .photo
        guard let captureDevice = AVCaptureDevice.DiscoverySession(
                deviceTypes: [.builtInWideAngleCamera],
                mediaType: .video,
                position: .front).devices.first else { return }
        
        do {
            let input = try AVCaptureDeviceInput(device: captureDevice)
            captureSession.addInput(input)
        } catch {}

        captureSession.commitConfiguration()
        
        previewLayer.videoGravity = .resizeAspectFill
        previewLayer.backgroundColor = UIColor.black.cgColor
        previewLayer.connection?.videoOrientation = 
            .init(rawValue: UIDevice.current.orientation.rawValue)!
        
        // ここがポイント #############################################################
        if #available(iOS 14.5, *) {
            // センターフレームを有効にする
            AVCaptureDevice.centerStageControlMode = .cooperative
            AVCaptureDevice.isCenterStageEnabled = true
            
            // センターフレームが使えるフォーマットをアクティブにする
            let width = \AVCaptureDevice.Format.formatDescription.dimensions.width
            if let format = captureDevice.formats
                .filter(\.isCenterStageSupported)
                .max(by: { $0[keyPath: width] < $1[keyPath: width] }) {
                do {
                    try captureDevice.lockForConfiguration()
                    captureDevice.activeFormat = format
                    captureDevice.unlockForConfiguration()
                } catch {}
            }
        }
        // ##########################################################################
    }
    
    func startRunning() {
        if captureSession.isRunning { return }
        captureSession.startRunning()
    }
    
    func stopRunning() {
        if !captureSession.isRunning { return }
        captureSession.stopRunning()
    }
}
// SwiftUIでカメラの映像を表示する
struct CenterStageView: View {
    @StateObject var viewModel = VideoCaptureViewModel()

    var body: some View {
        ZStack(alignment: .top) {
            CALayerView(caLayer: viewModel.previewLayer)
                .edgesIgnoringSafeArea(.all)
                .onAppear(perform: viewModel.startRunning)
                .onDisappear(perform: viewModel.stopRunning)

            if #available(iOS 14.5, *) {
                Toggle(isOn: .init(get: { AVCaptureDevice.isCenterStageEnabled },
                                   set: { AVCaptureDevice.isCenterStageEnabled = $0 })) {
                    Text("🕺Center Stage💃")
                        .font(.system(size: 120, weight: .heavy))
                        .foregroundColor(.red)
                }
            }
        }
    }
}
UIKitのラップ部分 (本質じゃない)
struct CALayerView: UIViewControllerRepresentable {
    var caLayer: CALayer
    
    func makeUIViewController(context: Context) -> LayerViewController {
        LayerViewController(caLayer: caLayer)
    }
    
    func updateUIViewController(_ uiViewController: LayerViewController,
                                context: Context) {}
    
    final class LayerViewController: UIViewController {
        let caLayer: CALayer

        init(caLayer: CALayer) {
            self.caLayer = caLayer
            super.init(nibName: nil, bundle: nil)
        }

        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        override func viewDidLoad() {
            super.viewDidLoad()
            view.layer.addSublayer(caLayer)
        }

        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            caLayer.frame = view.layer.bounds
        }
    }
}

まとめ

フラグの制御だけで簡単に人のトラッキングができて便利。
顔を隠すとトラッキングしなくなるのでフェイストラッキング的なことをやってるのかな。

蛇足

センターフレームを有効にするとズームできる量に制限が出たり、デプスや geometricDistortionCorrectionEnabled を使うとセンターフレームが無効になる仕様のようです。

https://developer.apple.com/documentation/avfoundation/avcapturedevice/format/3738423-videominzoomfactorforcenterstage
https://developer.apple.com/documentation/avfoundation/avcapturedevice/3194614-geometricdistortioncorrectionsup

また、英語だと設定ページはこんな表記。

日本語でも英語でも「ビデオ通話」という文言が入ってるけど、特に通話してなくても使えるので気になってます。

検証環境

  • iPad Pro 12.9 インチ (第 5 世代)
  • iPadOS 14.6