Después de solicitar y obtener los permisos necesarios, tu app podrá acceder al hardware de los lentes de audio o los lentes de pantalla. La clave para acceder al hardware de los lentes (en lugar del hardware del teléfono) es usar un contexto proyectado.
Existen dos formas principales de obtener un contexto proyectado, según dónde se ejecute tu código:
Obtén un contexto proyectado si tu código se ejecuta en una actividad proyectada
Si el código de tu app se ejecuta desde tu actividad proyectada, su propio contexto de actividad ya es un contexto proyectado. En esta situación, las llamadas realizadas dentro de esa actividad ya pueden acceder al hardware de los lentes.
Obtén un contexto proyectado para el código que se ejecuta en un componente de la app para teléfonos
Si una parte de tu app fuera de la actividad proyectada (como una actividad telefónica o un servicio) necesita acceder al hardware de los lentes, debe obtener explícitamente un contexto proyectado. Para ello, usa el método 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 } }
Cómo verificar la validez
Encapsula la llamada createProjectedDeviceContext dentro de ProjectedContext.isProjectedDeviceConnected. Mientras este método devuelve true, el contexto proyectado sigue siendo válido para el dispositivo conectado, y la actividad o el servicio de la app para teléfonos (como un CameraManager) pueden acceder al hardware de los lentes con IA.
Limpieza al desconectar
El contexto proyectado está vinculado al ciclo de vida del dispositivo conectado, por lo que se destruye cuando se desconecta el dispositivo. Cuando se desconecta el dispositivo, ProjectedContext.isProjectedDeviceConnected devuelve false. Tu app debe detectar este cambio y limpiar los servicios del sistema (como un CameraManager) o los recursos que creó con ese contexto proyectado.
Reinicializar al reconectar
Cuando los lentes se vuelven a conectar, tu app puede obtener otra instancia de contexto proyectado con createProjectedDeviceContext y, luego, reinicializar cualquier servicio o recurso del sistema con el nuevo contexto proyectado.
Graba audio con el micrófono de los lentes
Puedes grabar audio desde los lentes con dos métodos distintos:
- Usa un contexto proyectado.
- Usar el perfil de manos libres (HFP) de Bluetooth
Elige un método de grabación
El método que elijas dependerá de si necesitas procesamiento de audio de alta fidelidad específico para XR o entrada de audio Bluetooth estándar.
| Método de registro | Acceso al micrófono | Caso de uso común |
|---|---|---|
Contexto proyectado |
Varios micrófonos |
La grabación con un contexto proyectado permite que tu app acceda a varios micrófonos de los lentes y a sus funciones de hardware especializadas, como las siguientes:
|
HFP de Bluetooth |
Micrófono único |
Se basa en el perfil de manos libres (HFP) de Bluetooth para ofrecer compatibilidad inmediata. En este modo, los lentes se conectan al teléfono con los perfiles estándar de auriculares y de perfil de distribución de audio avanzado (A2DP), y funcionan como un periférico Bluetooth típico. Si tu app ya está diseñada para la grabación estándar por Bluetooth, puedes usar este método para grabar audio desde los lentes sin integrar ninguna capacidad específica de XR. |
Graba audio con un contexto proyectado
Para grabar audio con un contexto proyectado, primero solicita los permisos de tiempo de ejecución necesarios y, luego, graba el audio con la API de AudioRecord, como se describe en las siguientes secciones.
Cómo solicitar permisos de tiempo de ejecución
Para acceder a varios micrófonos en los lentes, debes solicitar permisos de audio específicamente para el dispositivo proyectado. El permiso estándar RECORD_AUDIO con alcance para el teléfono que el usuario otorgó a tu app en su dispositivo móvil no es suficiente.
Sigue estos pasos para solicitar los permisos:
- Declara el permiso
RECORD_AUDIOen el archivo de manifiesto de tu app. Solicita los permisos con alcance para el dispositivo proyectado de una de las siguientes maneras, según dónde se ejecute tu código:
- Ejecución de código desde una actividad proyectada: Usa
ActivityResultLauncherconProjectedPermissionsResultContract. Para obtener más información sobre el uso de este método, consulta la sección Cómo registrar el objeto de lanzamiento de permisos y las secciones posteriores de la guía para solicitar permisos de hardware. - Código que se ejecuta desde una actividad del teléfono host: Usa
Activity#requestPermissions(permissions, requestCode, deviceId)y proporciona el ID del dispositivo que obtuviste de tuprojectedDeviceContext, como se describe en la sección Comprende el flujo del usuario de la solicitud de permiso de la guía para solicitar permisos de hardware.
- Ejecución de código desde una actividad proyectada: Usa
Inicializa AudioRecord con un contexto proyectado
Para asegurarte de que el audio se grabe desde los lentes y no desde el teléfono host, debes asociar el objeto AudioRecord con el contexto del dispositivo proyectado.
El siguiente código usa AudioRecord.Builder y pasa projectedDeviceContext al método 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()
Puntos clave sobre el código
Puedes configurar la fuente de audio como
CAMCORDER,VOICE_RECOGNITION,VOICE_COMMUNICATIONoUNPROCESSEDpara adaptar el procesamiento de audio a tu caso de uso específico.Por ejemplo, usa
VOICE_COMMUNICATIONsi tu caso de uso necesita reducción de ruido automática.VOICE_RECOGNITIONse procesa con cancelación de eco acústico (AEC). Si necesitas audio sin procesar ni alterar, seleccionaUNPROCESSEDoCAMCORDER.Para garantizar la compatibilidad con los lentes, el objeto
audioFormatdebe definir una frecuencia de muestreo de 16 kHz y una configuración de canales mono o estéreo (conCHANNEL_IN_MONOoCHANNEL_IN_STEREO).Si bien no hay un requisito fijo para el tamaño del búfer, obtén el tamaño mínimo del búfer para minimizar la latencia percibida.
Limpia después de usarla
Cuando tu app ya no necesite el micrófono o cuando se detenga la actividad, llama a stop y release en el objeto AudioRecord.
Verifica los permisos de tiempo de ejecución antes de grabar
Antes de llamar a startRecording, verifica que el usuario haya otorgado el permiso de acceso al micrófono para los lentes con el contexto proyectado.
Cómo grabar audio con HFP de Bluetooth
Para grabar audio con HFP de Bluetooth, primero solicita los permisos de tiempo de ejecución necesarios y, luego, graba el audio con la API de AudioManager, como se describe en las siguientes secciones.
Solicita permisos
Al igual que con cualquier dispositivo de audio Bluetooth estándar, los permisos relacionados con RECORD_AUDIO, BLUETOOTH_CONNECT y otros se controlan desde el teléfono y no desde el dispositivo conectado (como anteojos de audio o anteojos con pantalla).
Sigue estos pasos para solicitar los permisos:
Declara los siguientes permisos en el archivo de manifiesto de tu app:
Solicita los permisos
RECORD_AUDIOyBLUETOOTH_CONNECTen el tiempo de ejecución con el flujo de permisos estándar de Android.
Cómo usar AudioManager para enrutar el audio
Después de que el usuario le otorgue a tu app los permisos de tiempo de ejecución necesarios, usa la API de AudioManager para configurar el dispositivo de comunicación en TYPE_BLUETOOTH_SCO y enrutar el audio a través de HFP de Bluetooth. Esto indica al sistema que recupere el audio del dispositivo periférico 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()
Cómo capturar una imagen con la cámara de los lentes
Para capturar una imagen con la cámara de los lentes, configura y vincula el caso de uso ImageCapture de CameraX a la cámara de los lentes con el contexto correcto para tu app:
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)) }
Puntos clave sobre el código
- Obtiene una instancia de
ProcessCameraProvidercon el contexto del dispositivo proyectado. - Dentro del alcance del contexto proyectado, la cámara principal de los lentes, que apunta hacia afuera, se asigna a
DEFAULT_BACK_CAMERAcuando se selecciona una cámara. - Una verificación previa a la vinculación usa
cameraProvider.hasCamera(cameraSelector)para verificar que la cámara seleccionada esté disponible en el dispositivo antes de continuar. - Usa Camera2 Interop con
Camera2CameraInfopara leer elCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPsubyacente, lo que puede ser útil para verificaciones avanzadas de las resoluciones admitidas. - Se crea un
ResolutionSelectorpersonalizado para controlar con precisión la resolución de la imagen de salida paraImageCapture. - Crea un caso de uso de
ImageCaptureque se configura con unResolutionSelectorpersonalizado. - Vincula el caso de uso de
ImageCaptureal ciclo de vida de la actividad. Esto administra automáticamente la apertura y el cierre de la cámara según el estado de la actividad (por ejemplo, detiene la cámara cuando se pausa la actividad).
Después de configurar la cámara de los lentes, puedes capturar una imagen con la clase ImageCapture de CameraX. Consulta la documentación de CameraX para obtener información sobre cómo usar takePicture para capturar una imagen.
Cómo grabar un video con la cámara de los lentes
Para capturar un video en lugar de una imagen con la cámara de los lentes, reemplaza los componentes ImageCapture por los componentes VideoCapture correspondientes y modifica la lógica de ejecución de la captura.
Los principales cambios implican usar un caso de uso diferente, crear un archivo de salida diferente y, luego, iniciar la captura con el método de grabación de video adecuado.
Para obtener más información sobre la API de VideoCapture y cómo usarla, consulta la documentación de captura de video de CameraX.
En la siguiente tabla, se muestran la resolución y la velocidad de fotogramas recomendadas según el caso de uso de tu app:
| Caso de uso | Solución | Velocidad de fotogramas |
|---|---|---|
| Comunicación por video | 1280 x 720 | 15 FPS |
| Visión artificial | 640 × 480 | 10 FPS |
| Transmisión de video con IA | 640 × 480 | 1 FPS |
Cómo acceder al hardware de un teléfono desde una actividad proyectada
Una actividad proyectada también puede acceder al hardware del teléfono (como la cámara o el micrófono) con createHostDeviceContext(context) para obtener el contexto del dispositivo host (teléfono):
@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 } }
Cuando accedas a hardware o recursos específicos del dispositivo host (teléfono) en una app híbrida (una app que contiene experiencias para dispositivos móviles y anteojos), debes seleccionar de forma explícita el contexto correcto para asegurarte de que tu app pueda acceder al hardware correcto:
- Usa el contexto
ActivitydelActivitydel teléfono o elProjectedContext.createHostDeviceContextpara obtener el contexto del teléfono. - No uses
getApplicationContextporque el contexto de la aplicación puede devolver incorrectamente el contexto de los lentes si una actividad proyectada fue el componente que se inició más recientemente.