Depois de solicitar e receber as permissões necessárias, seu app poderá acessar o hardware dos óculos com áudio ou óculos com tela virtual. A chave para acessar o hardware dos óculos (em vez do hardware do smartphone) é usar um contexto projetado.
Há duas maneiras principais de conseguir um contexto projetado, dependendo de onde o código está sendo executado:
Receber um contexto projetado se o código estiver sendo executado em uma atividade projetada
Se o código do app estiver sendo executado em uma atividade projetada, o contexto da atividade já será um contexto projetado. Nesse cenário, as chamadas feitas nessa atividade já podem acessar o hardware dos óculos.
Receber um contexto projetado para código em execução em um componente de app para smartphone
Se uma parte do app fora da atividade projetada (como uma atividade ou um serviço de smartphone) precisar acessar o hardware dos óculos, ela precisará receber um contexto projetado explicitamente. Para fazer isso, use o
createProjectedDeviceContext método:
@RequiresApi(Build.VERSION_CODES.BAKLAVA) @OptIn(ExperimentalProjectedApi::class, ExperimentalCoroutinesApi::class) private fun monitorProjectedConnectivity(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. // While this method returns true, the projected context remains valid. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // From a phone Activity or Service, get a context for the audio and display glasses. // Re-initialize on reconnect: Obtain another context instance. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create projected context", e) return@collectLatest } // Use the projectedContext to initialize system services (e.g., CameraManager). Log.i(TAG, "Projected device connected. Initializing hardware...") } else { // The projected context is destroyed when the device disconnects. // Clean up on disconnect: Listen for 'false' and release resources. Log.i(TAG, "Projected device disconnected. Cleaning up hardware resources...") } } } }
Verificar a validade
Encapsule a chamada createProjectedDeviceContext em
ProjectedContext.isProjectedDeviceConnected. Enquanto esse método retornar true, o contexto projetado permanecerá válido para o dispositivo conectado, e a atividade ou o serviço do app para smartphone (como um CameraManager) poderá acessar o hardware dos óculos de IA.
Limpar ao desconectar
O contexto projetado está vinculado ao ciclo de vida do dispositivo conectado. Portanto, ele é destruído quando o dispositivo é desconectado. Quando o dispositivo é desconectado,
ProjectedContext.isProjectedDeviceConnected retorna false. Seu app precisa detectar essa mudança e limpar todos os serviços do sistema (como um CameraManager) ou recursos criados usando esse contexto projetado.
Reinicializar ao reconectar
Quando os óculos se reconectarem, seu app poderá receber outra instância de contexto projetado usando createProjectedDeviceContext e, em seguida, reinicializar todos os serviços ou recursos do sistema usando o novo contexto projetado.
Gravar áudio com o microfone dos óculos
É possível gravar áudio dos óculos usando dois métodos distintos:
- Usar um contexto projetado.
- Usar o perfil viva-voz (HFP, na sigla em inglês) do Bluetooth.
Escolher um método de gravação
O método escolhido depende de você precisar de processamento de áudio de alta fidelidade e específico para XR ou de entrada de áudio Bluetooth padrão.
| Método de gravação | Acesso ao microfone | Caso de uso comum |
|---|---|---|
Contexto projetado |
Vários microfones |
A gravação usando um contexto projetado permite que seu app acesse vários microfones dos óculos e os recursos de hardware especializados, como:
|
HFP Bluetooth |
Microfone único |
Depende do perfil viva-voz (HFP) do Bluetooth para compatibilidade imediata. Nesse modo, os óculos se conectam ao smartphone usando os perfis padrão de fone de ouvido e de distribuição de áudio avançado (A2DP, na sigla em inglês) profiles, funcionando como um periférico Bluetooth típico. Se o app já estiver projetado para gravação Bluetooth padrão, você poderá usar esse método para gravar áudio dos óculos sem integrar recursos específicos de XR. |
Gravar áudio usando um contexto projetado
Para gravar áudio usando um contexto projetado, primeiro solicite as permissões de execução necessárias
e, em seguida, grave o áudio usando a API AudioRecord, conforme
descrito nas seções a seguir.
Solicitar permissões de execução
Para acessar vários microfones nos óculos, você precisa solicitar permissões de áudio especificamente para o dispositivo projetado. A permissão RECORD_AUDIO padrão e com escopo de smartphone que um usuário concedeu ao app no dispositivo móvel não é suficiente.
Siga estas etapas para solicitar as permissões:
- Declare a permissão
RECORD_AUDIOno arquivo de manifesto do app. Solicite as permissões com escopo de dispositivo projetado de uma das seguintes maneiras, dependendo de onde o código está sendo executado:
- Código em execução em uma atividade projetada: use o
ActivityResultLaunchercom oProjectedPermissionsResultContract. Para mais informações sobre como usar esse método, consulte a seção Registrar o iniciador de permissões e as seções subsequentes no guia para solicitar permissões de hardware. - Código em execução em uma atividade de smartphone host: use
Activity#requestPermissions(permissions, requestCode, deviceId)e forneça o ID do dispositivo recebido doprojectedDeviceContext, conforme descrito na seção Entender o fluxo de usuários de solicitação de permissão do guia para solicitar permissões de hardware.
- Código em execução em uma atividade projetada: use o
Inicializar o AudioRecord com um contexto projetado
Para garantir que o áudio seja gravado dos óculos e não do smartphone host, você precisa associar o objeto AudioRecord ao contexto do dispositivo projetado.
O código a seguir usa o AudioRecord.Builder e transmite o
projectedDeviceContext para o setContext método:
// 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()
Principais pontos sobre o código
Você pode definir a fonte de áudio como
CAMCORDER,VOICE_RECOGNITION,VOICE_COMMUNICATION, ouUNPROCESSEDpara adaptar o processamento de áudio ao seu caso de uso específico.Por exemplo, use
VOICE_COMMUNICATIONse o caso de uso precisar de redução de ruído automática ou isolar a voz do usuário.VOICE_RECOGNITIONé processado com cancelamento de eco acústico (AEC, na sigla em inglês), que pode ser útil quando há reprodução de áudio simultânea durante a gravação. E, se você precisar de áudio bruto e inalterado, selecioneUNPROCESSEDouCAMCORDER.Para garantir a compatibilidade com os óculos, o objeto
audioFormatprecisa definir uma taxa de amostragem de 16 kHz e uma configuração de canal mono ou estéreo (usandoCHANNEL_IN_MONOouCHANNEL_IN_STEREO).Use
AudioRecord.getMinBufferSize()para determinar o tamanho mínimo do buffer para criar o objetoAudioRecord. No entanto, para evitar quedas de áudio dos óculos, leia esse buffer em blocos curtos e frequentes (idealmente fatias de 20 ms) em vez de esperar que o buffer inteiro seja preenchido.
Limpar após o uso
Quando o app não precisar mais do microfone ou quando a atividade for interrompida,
chame stop e release no objeto AudioRecord.
Verificar permissões de execução antes da gravação
Antes de chamar startRecording, verifique se o usuário concedeu a
permissão do microfone para os óculos usando o contexto projetado.
Gravar áudio usando o HFP Bluetooth
Para gravar áudio usando o HFP Bluetooth, primeiro solicite as permissões de execução necessárias
e, em seguida, grave o áudio usando a API AudioManager, conforme
descrito nas seções a seguir.
Solicitar permissões
Assim como acontece com qualquer dispositivo de áudio Bluetooth padrão, as permissões RECORD_AUDIO,
BLUETOOTH_CONNECT e outras relacionadas são controladas pelo
smartphone e não pelo dispositivo conectado (como óculos com áudio ou óculos com tela virtual).
Siga estas etapas para solicitar as permissões:
Declare as seguintes permissões no arquivo de manifesto do app:
Solicite as permissões
RECORD_AUDIOeBLUETOOTH_CONNECTno momento da execução usando o fluxo de permissão padrão do Android.
Usar o AudioManager para encaminhar áudio
Depois que o usuário conceder ao app as permissões de execução necessárias, use a
AudioManager API para definir o dispositivo de comunicação como
TYPE_BLUETOOTH_SCO para encaminhar o áudio pelo HFP Bluetooth. Isso direciona o sistema para recuperar o áudio do 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()
Capturar uma imagem com a câmera dos óculos
Para capturar uma imagem com a câmera dos óculos, configure e vincule o caso de uso
ImageCapture ImageCapture do CameraX à câmera dos óculos usando o contexto correto
para seu app:
private fun startCameraOnGlasses(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // 1. Get the CameraProvider using the projected context. // When using the projected context, DEFAULT_BACK_CAMERA maps to the audio and display glasses' camera. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Projected context could not be created", e) return@collectLatest } 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)) } } } }
Principais pontos sobre o código
- Recebe uma instância do
ProcessCameraProviderusando o contexto do dispositivo projetado. - No escopo do contexto projetado, a câmera principal dos óculos, voltada para fora, é mapeada para
DEFAULT_BACK_CAMERAao selecionar uma câmera. - Uma verificação de pré-vinculação usa
cameraProvider.hasCamera(cameraSelector)para verificar se a câmera selecionada está disponível no dispositivo antes de continuar. - Usa a interoperabilidade do Camera2 com
Camera2CameraInfopara ler oCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPsubjacente, que pode ser útil para verificações avançadas em resoluções compatíveis. - Um
ResolutionSelectorpersonalizado é criado para controlar com precisão a resolução da imagem de saída paraImageCapture. - Cria um caso de uso
ImageCaptureconfigurado com umResolutionSelectorpersonalizado. - Vincula o caso de uso
ImageCaptureao ciclo de vida da atividade. Isso gerencia automaticamente a abertura e o fechamento da câmera com base no estado da atividade (por exemplo, interrompendo a câmera quando a atividade é pausada). - Configuração para hardware de fluxo único:o pipeline da câmera dos óculos é restrito a um único fluxo ativo por vez. Quando
ImageCaptureé vinculado sozinho, o CameraX anexa um fluxo de repetição interno (MeteringRepeating) para foco e medição por padrão, o que faz com que o pipeline de fluxo único seja interrompido. Para vincularImageCapturesozinho sem um fluxo de visualização, desative o fluxo de repetição forçado definindosetRepeatingStreamForced(false)emCameraXConfig.Builder(por exemplo, implementandoCameraXConfig.Providerna classeApplicationou configurandoProcessCameraProviderantes da inicialização).
Depois que a câmera dos óculos estiver configurada, você poderá capturar uma imagem com a classe ImageCapture do CameraX. Consulte a documentação do CameraX para saber
como usar takePicture para capturar uma imagem.
Capturar um vídeo com a câmera dos óculos
Para capturar um vídeo em vez de uma imagem com a câmera dos óculos, substitua os
ImageCapture componentes pelos componentes VideoCapture correspondentes
e modifique a lógica de execução da captura.
As principais mudanças envolvem o uso de um caso de uso diferente, a criação de um arquivo de saída diferente e a inicialização da captura usando o método de gravação de vídeo apropriado.
Para mais informações sobre a API VideoCapture e como usá-la, consulte a
documentação de captura de vídeo do CameraX.
A tabela a seguir mostra a resolução e o frame rate recomendados, dependendo do caso de uso do app:
| Caso de uso | Resolução | Frame rate |
|---|---|---|
| Comunicação por vídeo | 1280 x 720 | 15 FPS |
| Visão computacional | 640 x 480 | 10 FPS |
| Streaming de vídeo com IA | 640 x 480 | 1 FPS |
Acessar o hardware de um smartphone em uma atividade projetada
Uma atividade projetada também pode acessar o hardware do smartphone (como a
câmera ou o microfone) usando createHostDeviceContext(context) para receber
o contexto do dispositivo host (smartphone):
@OptIn(ExperimentalProjectedApi::class) private fun getPhoneContext(activity: ComponentActivity): Context? { return try { // From a projected Activity, get a context for the phone. ProjectedContext.createHostDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create host device context", e) null } }
Ao acessar hardware ou recursos específicos do dispositivo host (smartphone) em um app híbrido (um app que contém experiências para dispositivos móveis e óculos), você precisa selecionar explicitamente o contexto correto para garantir que o app possa acessar o hardware correto:
- Use o
Activitycontexto doActivitysmartphone ou oProjectedContext.createHostDeviceContextpara receber o contexto do smartphone. - Não use
getApplicationContext, porque o contexto do aplicativo pode retornar incorretamente o contexto dos óculos se uma atividade projetada foi o componente lançado mais recentemente.