必要な権限をリクエストして付与されると、アプリは 音声メガネまたはディスプレイ メガネのハードウェアにアクセスできます。メガネのハードウェア(スマートフォンのハードウェアではなく)にアクセスする鍵は、 投影されたコンテキストを使用することです。
コードの実行場所に応じて、投影されたコンテキストを取得する主な方法は 2 つあります。
コードが投影されたアクティビティで実行されている場合は、投影されたコンテキストを取得する
アプリのコードが投影されたアクティビティ内から実行されている場合、その アクティビティ コンテキスト自体が投影されたコンテキストになります。このシナリオでは、そのアクティビティ内で行われた呼び出しは、メガネのハードウェアにアクセスできます。
スマートフォン アプリ コンポーネントで実行されているコードの投影されたコンテキストを取得する
投影されたアクティビティ以外のアプリの一部(スマートフォン アクティビティやサービスなど)がメガネのハードウェアにアクセスする必要がある場合は、投影されたコンテキストを明示的に取得する必要があります。これを行うには、
createProjectedDeviceContext メソッドを使用します。
@OptIn(ExperimentalProjectedApi::class) private fun getGlassesContext(context: Context): Context? { return try { // From a phone Activity or Service, get a context for the AI glasses. ProjectedContext.createProjectedDeviceContext(context) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create projected device context", e) null } }
有効性を確認する
createProjectedDeviceContext 呼び出しを
ProjectedContext.isProjectedDeviceConnected 内にラップします。このメソッドが true を返している間、投影されたコンテキストは接続されたデバイスに対して有効なままであり、スマートフォン アプリのアクティビティまたはサービス(CameraManager など)は AI メガネのハードウェアにアクセスできます。
切断時にクリーンアップする
投影されたコンテキストは接続されたデバイスのライフサイクルに関連付けられているため、デバイスが切断されると破棄されます。デバイスが切断されると、
ProjectedContext.isProjectedDeviceConnected は false を返します。アプリはこの変更をリッスンし、その投影されたコンテキストを使用してアプリが作成したシステム サービス(CameraManager など)またはリソースをクリーンアップする必要があります。
再接続時に再初期化する
メガネが再接続すると、アプリは別の投影された
コンテキスト インスタンスを createProjectedDeviceContext を使用して取得し、新しい投影されたコンテキストを使用してシステム サービスまたはリソースを
再初期化できます。
メガネのマイクで音声を録音する
メガネから音声を録音するには、次の 2 つの方法があります。
- 投影されたコンテキストを使用する。
- Bluetooth ハンズフリー プロファイル(HFP)を使用する。
録音方法を選択する
選択するメソッドは、高忠実度で XR 固有の音声処理が必要かどうか、または標準の Bluetooth 音声入力が必要かどうかによって異なります。
| 録音方法 | マイクへのアクセス | 一般的なユースケース |
|---|---|---|
投影されたコンテキスト |
複数のマイク |
投影されたコンテキストを使用して録音すると、アプリはメガネの複数のマイクと、次のような専用のハードウェア機能にアクセスできます。
|
Bluetooth HFP |
単一のマイク |
すぐに使用できる互換性については、Bluetooth ハンズフリー プロファイル(HFP)に依存します。このモードでは、メガネは標準のヘッドセット プロファイルと Advanced Audio Distribution Profile(A2DP)プロファイルを使用してスマートフォンに接続し、一般的な Bluetooth 周辺機器のように機能します。 アプリが標準の Bluetooth 録音用に設計されている場合は、この方法を使用して、XR 固有の機能を統合せずにメガネから音声を録音できます。 |
投影されたコンテキストを使用して音声を録音する
投影されたコンテキストを使用して音声を録音するには、まず必要な実行時の権限をリクエストし、次のセクションで説明するように AudioRecord API を使用して音声を録音します。
実行時の権限をリクエストする
メガネの複数のマイクにアクセスするには、投影されたデバイスに対して音声権限をリクエストする必要があります。ユーザーがモバイル デバイスでアプリに付与した標準のスマートフォン スコープの RECORD_AUDIO 権限では不十分です。
権限をリクエストする手順は次のとおりです。
- アプリのマニフェスト ファイルで
RECORD_AUDIO権限 を宣言します。 コードの実行場所に応じて、次のいずれかの方法で、投影されたデバイス スコープの権限をリクエストします。
- 投影されたアクティビティから実行されるコード:
ActivityResultLauncherでProjectedPermissionsResultContractを使用します。このメソッドの使用方法について詳しくは、ハードウェア権限のリクエストに関するガイドの権限ランチャーの登録セクションと以降のセクションをご覧ください。 - ホスト スマートフォン アクティビティから実行されるコード:
Activity#requestPermissions(permissions, requestCode, deviceId)を使用し、projectedDeviceContextから取得したデバイス ID を指定します。 ハードウェア権限のリクエストに関するガイドの権限リクエストのユーザーフローについて で説明されています。
- 投影されたアクティビティから実行されるコード:
投影されたコンテキストで AudioRecord を初期化する
ホスト スマートフォンではなくメガネから音声が録音されるようにするには、AudioRecord オブジェクトを投影されたデバイス コンテキストに関連付ける必要があります。
次のコードでは、AudioRecord.Builder を使用し、
projectedDeviceContext を setContext メソッドに渡します。
// Initialize AudioRecord with projected device context val audioRecord = AudioRecord.Builder() .setAudioSource(MediaRecorder.AudioSource.CAMCORDER) .setAudioFormat(audioFormat) .setBufferSizeInBytes(bufferSize) // pass in the projected device context .setContext(projectedDeviceContext) .build() audioRecord.startRecording()
コードに関する主なポイント
音声ソースを
CAMCORDER、VOICE_RECOGNITION、VOICE_COMMUNICATION、またはUNPROCESSEDに設定して、特定のユース ケースに合わせて音声処理を調整できます。たとえば、ユースケースで自動ノイズリダクションが必要な場合は、
VOICE_COMMUNICATIONを使用します。VOICE_RECOGNITIONは音響エコー キャンセラ(AEC)で処理されます。未加工の音声が必要な場合は、UNPROCESSEDまたはCAMCORDERを選択します。メガネとの互換性を確保するには、
audioFormatオブジェクトで 16 kHz のサンプルレートと、モノラルまたはステレオのチャンネル構成(CHANNEL_IN_MONOまたはCHANNEL_IN_STEREOを使用)を定義する必要があります。バッファサイズに固定の要件はありませんが、認識されるレイテンシを最小限に抑えるために最小バッファ サイズを取得します。
使用後にクリーンアップする
アプリでマイクが不要になった場合、またはアクティビティが停止した場合は、
AudioRecord オブジェクトで stop と release を呼び出します。
録音前に実行時の権限を確認する
startRecording を呼び出す前に、投影されたコンテキストを使用して、ユーザーがメガネの
マイクの権限を付与していることを確認します。
Bluetooth HFP を使用して音声を録音する
Bluetooth HFP を使用して音声を録音するには、まず必要な実行時の権限をリクエストし、次のセクションで説明するように AudioManager API を使用して音声を録音します。
権限をリクエストする
標準の Bluetooth オーディオ機器と同様に、RECORD_AUDIO, BLUETOOTH_CONNECT、その他の関連権限は、スマートフォンによって制御され、接続済みのデバイス(音声メガネやディスプレイ メガネなど)によって制御されるわけではありません。
権限をリクエストする手順は次のとおりです。
次の権限を宣言します アプリのマニフェスト ファイルで:
標準の Android 権限フローを使用して、実行時に
RECORD_AUDIO権限とBLUETOOTH_CONNECT権限の両方をリクエストします。
AudioManager を使用して音声をルーティングする
ユーザーがアプリに必要な実行時の権限を付与したら、
AudioManager API を使用して通信デバイスを
TYPE_BLUETOOTH_SCO に設定し、Bluetooth HFP を介して音声をルーティングします。これにより、Bluetooth 周辺機器から音声を取得するようにシステムに指示されます。
val audioManager = context.getSystemService(AudioManager::class.java) ?: return val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS) val hfpDevice = devices.find { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO } hfpDevice?.let { device -> val audioRecord = AudioRecord.Builder() .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) .setAudioFormat(audioFormat) .setBufferSizeInBytes(bufferSize) .build() // Route recording to the Bluetooth device audioRecord.setPreferredDevice(device) audioManager.setCommunicationDevice(device) audioRecord.startRecording()
メガネのカメラで画像をキャプチャする
メガネのカメラで画像をキャプチャするには、アプリの正しい
コンテキストを使用して、CameraX の
ImageCapture ユースケースをメガネのカメラに設定してバインドします。
private fun startCameraOnGlasses(activity: ComponentActivity) { // 1. Get the CameraProvider using the projected context. // When using the projected context, DEFAULT_BACK_CAMERA maps to the AI glasses' camera. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "AI Glasses context could not be created", e) return } val cameraProviderFuture = ProcessCameraProvider.getInstance(projectedContext) cameraProviderFuture.addListener({ val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA // 2. Check for the presence of a camera. if (!cameraProvider.hasCamera(cameraSelector)) { Log.w(TAG, "The selected camera is not available.") return@addListener } // 3. Query supported streaming resolutions using Camera2 Interop. val cameraInfo = cameraProvider.getCameraInfo(cameraSelector) val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo) val cameraCharacteristics = camera2CameraInfo.getCameraCharacteristic( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP ) // 4. Define the resolution strategy. val targetResolution = Size(1920, 1080) val resolutionStrategy = ResolutionStrategy( targetResolution, ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER ) val resolutionSelector = ResolutionSelector.Builder() .setResolutionStrategy(resolutionStrategy) .build() // 5. If you have other continuous use cases bound, such as Preview or ImageAnalysis, // you can use Camera2 Interop's CaptureRequestOptions to set the FPS val fpsRange = Range(30, 60) val captureRequestOptions = CaptureRequestOptions.Builder() .setCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange) .build() // 6. Initialize the ImageCapture use case with options. val imageCapture = ImageCapture.Builder() // Optional: Configure resolution, format, etc. .setResolutionSelector(resolutionSelector) .build() try { // Unbind use cases before rebinding. cameraProvider.unbindAll() // Bind use cases to camera using the Activity as the LifecycleOwner. cameraProvider.bindToLifecycle( activity, cameraSelector, imageCapture ) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(activity)) }
コードに関する主なポイント
ProcessCameraProviderを使用して **投影されたデバイス コンテキスト** のインスタンスを取得します。- 投影されたコンテキストのスコープ内で、カメラを選択すると、メガネの主要な外向きカメラが
DEFAULT_BACK_CAMERAにマッピングされます。 - 事前バインディング チェックでは、
cameraProvider.hasCamera(cameraSelector)を使用して、選択したカメラがデバイスで使用可能であることを確認してから処理を進めます。 Camera2CameraInfoで Camera2 Interop を使用して、 基盤となるCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPを読み取ります。 これは、サポートされている解像度の高度なチェックに役立ちます。- カスタムの
ResolutionSelectorは、出力 画像解像度をImageCaptureのために正確に制御するために構築されています。 - カスタムの
ResolutionSelectorで構成されたImageCaptureユースケースを作成します。 ImageCaptureユースケースをアクティビティのライフサイクルにバインドします。これにより、アクティビティの状態に基づいてカメラの開閉が自動的に管理されます(たとえば、アクティビティが一時停止されたときにカメラを停止します)。
メガネのカメラを設定したら、CameraX の ImageCapture クラスで画像をキャプチャできます。画像をキャプチャする方法については、CameraX のドキュメントをご覧ください。
takePicture
メガネのカメラで動画をキャプチャする
メガネのカメラで画像の代わりに動画をキャプチャするには、
ImageCapture コンポーネントを対応する VideoCapture コンポーネント
に置き換え、キャプチャ実行ロジックを変更します。
主な変更点は、別のユースケースを使用し、別の出力ファイルを作成し、適切な動画録画方法を使用してキャプチャを開始することです。
VideoCapture API とその使用方法について詳しくは、
CameraX の動画キャプチャのドキュメントをご覧ください。
次の表に、アプリのユースケースに応じた推奨解像度とフレームレートを示します。
| ユースケース | 解決策 | フレームレート |
|---|---|---|
| ビデオ通信 | 1280 x 720 | 15 fps |
| コンピュータ ビジョン | 640 x 480 | 10 fps |
| AI 動画ストリーム | 640 x 480 | 1 fps |
投影されたアクティビティからスマートフォンのハードウェアにアクセスする
投影されたアクティビティは、createHostDeviceContext(context) を使用してホスト デバイス(スマートフォン)のコンテキストを取得することで、スマートフォンのハードウェア(
カメラやマイクなど)にもアクセスできます。
@OptIn(ExperimentalProjectedApi::class) private fun getPhoneContext(activity: ComponentActivity): Context? { return try { // From an AI glasses Activity, get a context for the phone. ProjectedContext.createHostDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create host device context", e) null } }
ハイブリッド アプリ(モバイルとグラスの両方のエクスペリエンスを含むアプリ)でホスト デバイス(スマートフォン)に固有のハードウェアまたはリソースにアクセスする場合は、アプリが正しいハードウェアにアクセスできるように、正しいコンテキストを明示的に選択する必要があります。
- スマートフォンのコンテキストを取得するには、スマートフォンの
ActivityのActivityコンテキストまたはProjectedContext.createHostDeviceContextを使用します。 getApplicationContextは使用しないでください。投影されたアクティビティが最後に起動されたコンポーネントである場合、アプリケーション コンテキスト がメガネのコンテキストを誤って返す可能性があります。