WWDC Quick Look 💓 By SwiftGGTeam
Create camera extensions with Core Media IO

Create camera extensions with Core Media IO

元の動画を見る

ハイライト

macOS 12.3 では DAL plug-in に代わり SystemExtensions ベースの Camera Extensions が導入され、サードパーティのソフトウェアカメラ、ハードウェアカメラ、クリエイティブカメラをサンドボックス化された daemon として FaceTime などすべてのカメラ App から利用できます。


主要内容

macOS でサードパーティカメラを作るとき、これまでよく使われていたのが DAL plug-in です。macOS 10.7 から存在し、ハードウェアカメラのドライブや仮想カメラの公開ができます。

問題はセキュリティ境界です。DAL plug-in はサードパーティコードを App プロセスに直接読み込みます。プラグインにバグがあれば App がクラッシュし、悪意があれば App が攻撃面に晒されます。そのため FaceTime、QuickTime Player、Photo Booth はサポートせず、多くのサードパーティカメラ App も library validation や SIP を無効にしないと読み込みません(00:56)。

Camera Extensions は macOS 12.3 で導入された新アーキテクチャです。拡張コードは独立したサンドボックス daemon で動作し、buffer は App に渡す前に検証されます。フレームワークが拡張と App 間の IPC を担い、複数クライアントへ buffer を配布します(01:50)。

ユーザーから見れば通常のカメラと同じです。Camera Extension は既存の AVFoundation capture API で公開され、カメラ App では内蔵カメラのように表示され、FaceTime のカメラ選択にも現れます(03:36)。

Session では用途を 3 類に分けます。

第 1 は純ソフトウェアカメラです。カラーバー、テストパターン、異なるフレームレート・解像度のプログラム生成映像、または事前レンダリングコンテンツの再生で A/V 同期を検証できます(04:02)。

第 2 はハードウェアカメラドライバです。DriverKit Extension と組み合わせてハードウェアにアクセスするか、必要な場合は legacy IOVideoFamily kext を使います。非標準 USB Video Class カメラでは、特定の vendor ID と product ID を宣言して Apple のデフォルト UVC extension を上書きできます(04:30)。

第 3 はクリエイティブカメラです。別の物理カメラ映像を読み、フィルターや合成を行い、結果を新しいカメラ stream として公開します。CIFilterCam デモがこの模式で、設定 App がソースカメラと Core Image フィルターを選び、FaceTime と Photo Booth が更新された仮想カメラ映像を同時に見ます(06:05)。

詳細

1. 配布モデル:拡張は App と一緒に配布

Camera Extension は SystemExtensions フレームワーク上に構築されます。拡張実行ファイルは App bundle 内に置きます。App が SystemExtensions を呼んでインストール・アップグレード・ダウングレードし、App 削除時に SystemExtensions が全ユーザー向けに Camera Extension をアンインストールします(07:29)。

別インストーラが不要で、App Store 配布も可能です。

概念フロー:インストールボタンが system extension 有効化リクエストを起動
1. App bundle 内の Camera Extension を特定
2. 拡張を activate する OSSystemExtensionRequest を作成
3. OSSystemExtensionRequestDelegate でインストール状態を記録
4. SystemExtensions フレームワークにリクエストを渡す

キーポイント:

  • OSSystemExtensionRequest は Session のインストール/アンインストールボタンが作る有効化/無効化リクエストに対応します。
  • リクエスト対象は App bundle に埋め込まれた Camera Extension です。
  • OSSystemExtensionRequestDelegate はインストール状態を受け取り、Session の例では delegate メソッドで結果を記録します。
  • 概念フローであり、identifier とエラー処理は target 設定に従います。

開発でよくある落とし穴:system extension は /Applications にある App からしかインストールできません。Session デモでは直接実行後に失敗し、/Applications に移したら成功しました(12:57)。

Info.plist にも重要設定があります。Camera Extension の Info.plist は CMIOExtensionMachServiceName で自身を識別し、このキーがない拡張は registerassistant が起動しません。拡張 entitlements で App Sandbox を有効にし、App Group プレフィックスは Mach service name と一致させる必要があります(11:01)。

概念設定チェックリスト:
- Info.plist: CMIOExtensionMachServiceName
- Info.plist: システム拡張の用途説明
- Entitlements: App Sandbox = YES
- Entitlements: App Group プレフィックスが MachServiceName と一致

キーポイント:

  • CMIOExtensionMachServiceName は registerassistant が拡張を見つけ起動する入口です。
  • 用途説明はシステム拡張の承認フローに表示されます。
  • App Sandbox は拡張実行の前提です。
  • App Group 名の不一致は拡張検証失敗の原因になります。

2. Provider、Device、Stream:3 オブジェクトで 1 台のカメラ

CoreMedia IO Extension の主要クラスは Provider、Device、Stream です。Provider が Device を所有し、Device が Stream を所有します。3 つとも properties を持てます(22:04)。

作成時はそれぞれ ProviderSourceDeviceSourceStreamSource を提供します。Source がプロトコル実装を担い、フレームワークがボイラープレートを処理します。

概念フロー:Provider がサービス入口
1. CMIOExtensionProviderSource に準拠する source オブジェクトを実装
2. この source で CMIOExtensionProvider を作成
3. Provider の startService を呼び、provider インスタンスを渡す
4. 必要に応じて provider 下に device と stream を公開

キーポイント:

  • CMIOExtensionProviderSource は拡張動作を実装する source プロトコルの一つです。
  • CMIOExtensionProvider はデバイス公開を管理する拡張の底层オブジェクトです。
  • startService はサービス起動の入口で、Session は provider を渡すと明記しています。
  • 実プロジェクトでは device source、stream source も作り、device に stream を追加します。

Provider はホットプラグなどに応じて device を追加・削除できます。クライアントプロセス接続通知も受け取り、特定 App への device 公開を制御できます(22:35)。

Device は stream を管理します。localized name、UUID 形式の device ID、任意の legacy ID があります。localizedName は AVFoundation の AVCaptureDevice.localizedName に、deviceIDAVCaptureDevice.uniqueIdentifier に渡ります。旧 DAL plug-in 移行で公開済み identifier を維持するには legacy device ID を提供します(23:40)。

概念設定:Device 層は安定した名称と識別子が必要
- localizedName: カメラ選択でユーザーに見える名称
- deviceID: UUID 形式のデバイス識別子
- legacyDeviceID: DAL plug-in 移行時に旧 uniqueIdentifier を保持

キーポイント:

  • localizedName はカメラ選択での表示名に影響します。
  • deviceID は新アーキテクチャでの安定デバイス識別子です。
  • legacyDeviceID は旧 DAL plug-in 移行で互換が必要な場合のみ使います。
  • AVFoundation は最初の input stream だけを消費し、複数 input stream はすべて使われません。

Stream は実際に sample buffer を送るオブジェクトです。CMIOExtensionStreamFormat を公開し、有効フレームレートを定義し、active format を設定し、標準またはカスタム clock で buffer タイミングを駆動し、クライアントに sample buffer を渡します(25:44)。

概念設定:Stream 層はフォーマット、フレームレート、時間を記述
- stream formats: クライアントに公開する利用可能なビデオフォーマット
- active format index: 現在使用中のビデオフォーマット
- frame duration: 最大フレームレート
- max frame duration: 最小フレームレート
- clock: 各 sample buffer を駆動する時間源

キーポイント:

  • CMIOExtensionStreamFormatAVCaptureDeviceFormat にマッピングされます。
  • クライアントは active format index の読み書きで現在フォーマットを切り替えられます。
  • frame duration は最大フレームレート、max frame duration は最小フレームレートに対応します。
  • sample buffer の時間は標準またはカスタム clock が駆動します。

3. クリエイティブカメラ:設定 App が property で拡張を制御

CIFilterCam デモはクリエイティブカメラの対話を示します。設定 App にはソースカメラ選択、フィルター選択、bypass ボタン、ライブプレビューがあり、プレビューは AVCaptureVideoPreviewLayer で支えられます(15:02)。

有用なのは拡張側です。FaceTime と Photo Booth が CIFilterCam を選んだ状態で、設定 App がフィルターを変えると両 App が同時に新効果を見ます。ソースカメラを変えると CIFilterCam を使うすべての App が追従します(16:29)。

概念フロー:設定 App がクリエイティブカメラを制御
1. ユーザーが設定 App でソースカメラを選択
2. ユーザーが Core Image フィルターを選択
3. 設定 App が CoreMedia IO C property interface で拡張属性を書き込み
4. 拡張が新属性で後続 buffer を処理
5. CIFilterCam 利用中のすべての App が同期して変化を見る

キーポイント:

  • ソースカメラ選択はデモの「use this camera」設定変化に対応します。
  • フィルター選択は「use this other filter」設定変化に対応します。
  • 概念フローであり、Session はそのままコピーできる property 書き込みコードを提供していません。
  • AVFoundation はカスタム property をサポートしないため、設定 App は CoreMedia IO C API を使います。

CMIOExtension は DAL controls の代替を提供しません。自動露出、明るさ、シャープネス、パンズームなどは property としてモデル化すべきです。カスタム property は legacy CoreMedia IO C property interface にブリッジでき、名前は 4cc_ で始め、selector、scope、element をアンダースコア区切りでエンコードします(26:46)。

概念例:カスタム property 命名
4cc_cust_glob_0000

キーポイント:

  • 4cc_ は旧 C property address へのブリッジであることを示します。
  • cust は 4 文字 selector で、Session では custom の例です。
  • glob は global scope。scope は input や output もあり得ます。
  • 0000 は element。main element は通常 0 です。

クリエイティブカメラが拡張内で別のハードウェアカメラにアクセスするには macOS Ventura が必要です。拡張 entitlements に com.apple.security.device.camera、Info.plist に NSCameraUsageDescription を追加し、ユーザーに承認プロンプトが出ます(17:31)。

4. セキュリティ境界:daemon、registerassistantservice、TCC

Camera Extension の拡張コードは App プロセスに入りません。Apple の registerassistantservice は CMIOExtensionMachServiceName で拡張を識別し、role user _cmiodalassistants として起動します。sandboxd はカメラ向け sandbox profile を適用します(19:25)。

この profile は USB、Bluetooth、Wi-Fi クライアント、FireWire などの一般的なハードウェア通信と、拡張自身の container・tmp の読み書きを許可します。通常 App より厳格で、fork/exec/posix spawn による子プロセス、window server アクセス、フォアグラウンドユーザーアカウントへの接続、グローバル名前空間での Mach service 登録はできません(19:59)。

Camera Extension 実行境界:
App process
  -> CoreMedia IO / AVFoundation 互換レイヤー
  -> registerassistantservice
  -> サンドボックス化された camera extension daemon

キーポイント:

  • App は引き続き AVFoundation または CoreMedia IO 経由でカメラを見ます。
  • registerassistantservice は App と拡張間のプロキシです。
  • 拡張 daemon はサンドボックス分離され、サードパーティコードを App に読み込めません。
  • buffer は App に渡す前に検証と権限制御を受けます。

registerassistantservice は透明性、同意、制御ポリシーも担います。App が初めてカメラを使うときシステムがユーザーに尋ね、内蔵・サードパーティカメラの双方に consent が適用されます。拒否時はプロキシが buffer 配信を止めます。attribution により、どの App がどのカメラを使っているかをシステムが知り、daemon の消費電力を利用 App に帰属できます(21:04)。

5. Output device:CoreMedia IO はビデオを消費もできる

DAL plug-in のあまり知られていない能力が output device の公開です。カメラと逆に、App に映像を提供せず、App から送られる映像をリアルタイムで消費します。典型例は print-to-tape や SDI 入力付きプロ機器へのライブプレビュー送信です(28:44)。

output device に AVFoundation 相当 API はありません。フレーム送信には CoreMedia IO C API を直接使う必要があります(29:25)。

概念フロー:sink stream がビデオを消費
1. stream direction を sink に設定
2. クライアントが sample buffer を単純キューに入れる
3. 拡張が consumeSampleBuffer で buffer を消費
4. 消費後 notifyScheduledOutputChanged でクライアントに通知

キーポイント:

  • source stream はカメラのようにデータを生成します。
  • sink stream は出力デバイスのようにデータを消費します。
  • consumeSampleBuffer は Session で言及された拡張側の消費呼び出しです。
  • notifyScheduledOutputChanged はキュー状態変化をクライアントに通知します。

Session は最後に移行シグナルを示します。macOS 12.3 で DAL plug-in は deprecated となりビルド warning が出ます。Apple は macOS Ventura 以降の次の major release で DAL plug-in を完全無効化する予定です。DAL plug-in を保守するチームは Camera Extension への移行を始めるべきです(30:28)。

重要ポイント

  • やること:会議専用の仮想カメラを作る。 価値がある理由:Camera Extension は FaceTime などすべてのカメラ App に現れ、設定 App でフィルターを 1 回変えると全クライアントが同じ結果を見ます。 始め方:ソフトウェアカメラテンプレートで Provider、Device、Stream を構築し、Core Image でソースカメラ buffer を処理。設定 App は CoreMedia IO カスタム property でフィルター名を書き込む。

  • やること:非標準 USB カメラ向け macOS ドライバを作る。 価値がある理由:Apple の UVC extension は標準 UVC 向け。非標準プロトコルや UVC 外の機能は Camera Extension でデフォルト UVC extension を上書きできます。 始め方:「Overriding the default USB video class extension」を読み、最小 DEXT bundle の Info.plist に vendor ID と product ID を宣言する。

  • やること:テストパターンを自動再生するソフトウェアカメラを作る。 価値がある理由:純ソフトウェアカメラは固定パターンを異なるフレームレート・解像度で生成し、ビデオ会議・録画・A/V 同期を検証できます。 始め方:Xcode の Camera Extension target テンプレートから始め、ソフトウェア camera stream を残し sample buffer 生成ロジックを置き換える。

  • やること:旧 DAL plug-in を Camera Extension に移行する。 価値がある理由:DAL plug-in は deprecated で Ventura 以降の major release で無効化予定。Camera Extension は Apple App でも使えます。 始め方:旧デバイスの unique identifier を legacy device ID として再利用し、DAL controls を provider/device/stream properties に段階的にマッピングする。

関連セッション

コメント

GitHub Issues · utterances