Usar um contexto projetado para acessar hardware em óculos de áudio e óculos de exibição

Dispositivos XR relevantes
Estas orientações ajudam você a criar experiências para esses tipos de dispositivos XR.
Óculos de áudio e
display

Depois de solicitar e receber as permissões necessárias, seu app poderá acessar o hardware dos óculos de áudio ou de tela. A chave para acessar o hardware dos óculos (em vez do hardware do smartphone) é usar um contexto projetado.

Há duas maneiras principais de receber 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 na atividade projetada, o contexto de atividade dele 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 de smartphone ou um serviço) precisar acessar o hardware dos óculos, ela precisará obter explicitamente um contexto projetado. Para fazer isso, use o 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
    }
}

Verificar a validade

Encapsule a chamada createProjectedDeviceContext dentro do ProjectedContext.isProjectedDeviceConnected. Embora esse método retorne true, o contexto projetado permanece válido para o dispositivo conectado, e a atividade ou o serviço do app de smartphone (como um CameraManager) pode 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 se desconecta, ProjectedContext.isProjectedDeviceConnected retorna false. Seu app precisa detectar essa mudança e limpar todos os serviços do sistema (como um CameraManager) ou recursos que ele criou usando esse contexto projetado.

Reinicializar ao reconectar

Quando os óculos se reconectarem, o app poderá receber outra instância de contexto projetado usando createProjectedDeviceContext e, em seguida, reinicializar 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 diferentes:

Escolher um método de gravação

O método escolhido depende se você precisa 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 o app acesse vários microfones dos óculos e recursos de hardware especializados, como:

  • Espacialização específica de XR.
  • Redução de ruído avançada.
  • Separação de voz que distingue entre a voz do usuário e a de pessoas ao redor.
  • Manter o acesso à gravação em ambientes com vários dispositivos, mesmo quando os óculos não são o dispositivo Bluetooth ativo.

Bluetooth HFP

Microfone único

Usa o perfil viva-voz (HFP) do Bluetooth para compatibilidade imediata e pronta para uso. Nesse modo, os óculos se conectam ao smartphone usando os perfis padrão de fone de ouvido e perfil de distribuição de áudio avançado (A2DP), funcionando como um periférico Bluetooth típico.

Se o app já estiver projetado para gravação padrão por Bluetooth, use 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 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, peça permissões de áudio especificamente para o dispositivo projetado. A permissão padrão RECORD_AUDIO no escopo do smartphone que um usuário concedeu ao app no dispositivo móvel não é suficiente.

Siga estas etapas para solicitar as permissões:

  1. Declare a permissão RECORD_AUDIO no arquivo de manifesto do app.
  2. Solicite as permissões com escopo de dispositivo projetado de uma das seguintes maneiras, dependendo de onde o código está sendo executado:

Inicializar AudioRecord com um contexto projetado

Para garantir que o áudio seja gravado pelos óculos e não pelo smartphone host, associe o objeto AudioRecord ao contexto do dispositivo projetado.

O código a seguir usa o AudioRecord.Builder e transmite o projectedDeviceContext ao 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()

Pontos principais sobre o código
  • Você pode definir a origem do áudio como CAMCORDER, VOICE_RECOGNITION, VOICE_COMMUNICATION ou UNPROCESSED para adaptar o processamento de áudio ao seu caso de uso específico.

    Por exemplo, use VOICE_COMMUNICATION se o caso de uso precisar de redução de ruído automática. O VOICE_RECOGNITION é processado com cancelamento de eco acústico (AEC). Se você precisar de áudio bruto e sem alterações, selecione UNPROCESSED ou CAMCORDER.

  • Para garantir a compatibilidade com os óculos, o objeto audioFormat precisa definir uma taxa de amostragem de 16 kHz e uma configuração de canal mono ou estéreo (usando CHANNEL_IN_MONO ou CHANNEL_IN_STEREO).

  • Embora não haja um requisito fixo para o tamanho do buffer, receba o tamanho mínimo do buffer para minimizar a latência percebida.

Limpe 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 de gravar

Antes de chamar startRecording, verifique se o usuário concedeu a permissão de microfone para os óculos usando o contexto projetado.

Gravar áudio usando Bluetooth HFP

Para gravar áudio usando o HFP Bluetooth, primeiro solicite as permissões de execução necessárias e grave o áudio usando a API AudioManager, conforme descrito nas seções a seguir.

Solicitar permissões

Assim como qualquer dispositivo de áudio Bluetooth padrão, as permissões RECORD_AUDIO, BLUETOOTH_CONNECT e outras relacionadas são controladas pelo smartphone, não pelo dispositivo conectado (como óculos de áudio ou óculos de exibição).

Siga estas etapas para solicitar as permissões:

  1. Declare as seguintes permissões no arquivo de manifesto do app:

  2. Solicite as permissões RECORD_AUDIO e BLUETOOTH_CONNECT no tempo de execução usando o fluxo padrão de permissões do Android.

Usar o AudioManager para rotear o áudio

Depois que o usuário conceder ao app as permissões de execução necessárias, use a API AudioManager para definir o dispositivo de comunicação como TYPE_BLUETOOTH_SCO e rotear o áudio pelo HFP Bluetooth. Isso direciona o sistema para recuperar á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 ImageCapture caso de uso do CameraX à câmera dos óculos usando o contexto correto para seu 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))
}

Pontos principais sobre o código

  • Recebe uma instância do ProcessCameraProvider usando o contexto de dispositivo projetado.
  • No escopo do contexto projetado, a câmera principal dos óculos, que aponta para fora, é mapeada para o DEFAULT_BACK_CAMERA ao 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 Camera2CameraInfo para ler o CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP subjacente, o que pode ser útil para verificações avançadas em resoluções compatíveis.
  • Um ResolutionSelector personalizado é criado para controlar com precisão a resolução da imagem de saída para ImageCapture.
  • Cria um caso de uso de ImageCapture configurado com um ResolutionSelector personalizado.
  • Vincula o caso de uso ImageCapture ao 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).

Depois que a câmera dos óculos estiver configurada, você poderá capturar uma imagem com a classe ImageCapture do CameraX. Consulte a documentação da CameraX para saber como usar takePicture para capturar uma imagem.

Gravar 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 componentes ImageCapture pelos componentes VideoCapture correspondentes e modifique a lógica de execução da captura.

As principais mudanças envolvem usar um caso de uso diferente, criar um arquivo de saída diferente e iniciar a captura usando o método de gravação de vídeo adequado. 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 a taxa de frames recomendadas, dependendo do caso de uso do app:

Caso de uso Resolução Frame rate
Comunicação por vídeo 1280 x 720 15 QPS
Visão computacional 640 x 480 10 QPS
Streaming de vídeo com IA 640 x 480 1 QPS

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 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
    }
}

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), selecione explicitamente o contexto correto para garantir que o app possa acessar o hardware certo: