Acesso compartilhado à câmera com o ARCore

Este guia para desenvolvedores mostra as etapas para permitir a troca do seu app perfeitamente entre o controle exclusivo da câmera por meio do API Android Camera2 e compartilhar o acesso à câmera com o ARCore.

Neste tópico, pressupõe-se que você:

Criar e executar o aplicativo de amostra

Quando você cria e executa o app de exemplo Câmera compartilhada Java (em inglês), ele cria uma Sessão do ARCore que oferece suporte ao acesso compartilhado à câmera. O app não é iniciado em RA com o ARCore pausado.

Quando o app opera em modo não RA, o visor da câmera mostra uma cor sépia efeito Ao alternar para o modo RA, o efeito sépia é desativado conforme o app retorna o controle da câmera ao ARCore retomando a sessão pausada.

Você pode usar o interruptor de RA no app para mudar os modos. Durante a prévia, os dois modos mostra o número de frames contínuos capturados pela Camera2.

Para criar e executar o app de exemplo Java de câmera compartilhada:

  1. Faça o download e extraia o SDK do ARCore do Google para Android.

  2. Abra o Projeto samples/shared_camera_java.

  3. Verifique se o dispositivo Android está conectado à máquina de desenvolvimento via USB. Consulte Dispositivos compatíveis com o ARCore para mais detalhes.

  4. No Android Studio, clique em Run .

  5. Escolha seu dispositivo como destino da implantação e clique em OK para iniciar a app de exemplo no seu dispositivo.

  6. No dispositivo, confirme que você quer permitir que o app tire fotos e gravar vídeo.

  7. Se solicitado, atualize ou instale a versão mais recente do ARCore.

  8. Use a chave AR para alternar entre os modos RA e não RA.

Visão geral da ativação de um app para compartilhar o acesso à câmera com o ARCore

Siga estas etapas para implementar o acesso compartilhado à câmera com o ARCore no seu app. Todos os snippets de código estão disponíveis na SharedCameraActivity.java em shared_camera_java amostra.

Solicitar permissão de CAMERA

Para poder usar a câmera do dispositivo, o usuário precisa conceder a permissão CAMERA ao app. Os exemplos do ARCore incluem um CameraPermissionHelper, que fornece utilitários para solicitar a permissão correta para seu app.

protected void onResume() {
 
// Request the camera permission, if necessary.
 
if (!CameraPermissionHelper.hasCameraPermission(this)) {
     
CameraPermissionHelper.requestCameraPermission(this);
 
}
}
override fun onResume() {
 
// Request the camera permission, if necessary.
 
if (!CameraPermissionHelper.hasCameraPermission(this)) {
   
CameraPermissionHelper.requestCameraPermission(this)
 
}
}

Verifique se o ARCore está instalado e atualizado

O ARCore precisa estar instalado e atualizado antes de ser usado. O snippet a seguir mostra como solicitar uma instalação do ARCore se ele ainda não tiver sido instalado no dispositivo.

boolean isARCoreSupportedAndUpToDate() {
 
// Make sure that ARCore is installed and supported on this device.
 
ArCoreApk.Availability availability = ArCoreApk.getInstance().checkAvailability(this);
 
switch (availability) {
   
case SUPPORTED_INSTALLED:
     
return true;

   
case SUPPORTED_APK_TOO_OLD:
   
case SUPPORTED_NOT_INSTALLED:
       
// Requests an ARCore installation or updates ARCore if needed.
       
ArCoreApk.InstallStatus installStatus = ArCoreApk.getInstance().requestInstall(this, userRequestedInstall);
       
switch (installStatus) {
         
case INSTALL_REQUESTED:
           
return false;
         
case INSTALLED:
           
return true;
       
}
     
return false;

   
default:
     
// Handle the error. For example, show the user a snackbar that tells them
     
// ARCore is not supported on their device.
     
return false;
 
}
}
// Determine ARCore installation status.
// Requests an ARCore installation or updates ARCore if needed.
fun isARCoreSupportedAndUpToDate(): Boolean {
 
when (ArCoreApk.getInstance().checkAvailability(this)) {
   
Availability.SUPPORTED_INSTALLED -> return true

   
Availability.SUPPORTED_APK_TOO_OLD,
   
Availability.SUPPORTED_NOT_INSTALLED -> {
     
when(ArCoreApk.getInstance().requestInstall(this, userRequestedInstall)) {
       
InstallStatus.INSTALLED -> return true
       
else -> return false
     
}
   
}

   
else -> {
     
// Handle the error. For example, show the user a snackbar that tells them
     
// ARCore is not supported on their device.
     
return false
   
}
 
}
}

Criar uma sessão do ARCore compatível com o compartilhamento de câmera

Isso envolve criar a sessão e armazenar a referência e o ID do ARCore câmera compartilhada:

// Create an ARCore session that supports camera sharing.
sharedSession
= new Session(this, EnumSet.of(Session.Feature.SHARED_CAMERA))

// Store the ARCore shared camera reference.
sharedCamera
= sharedSession.getSharedCamera();

// Store the ID of the camera that ARCore uses.
cameraId
= sharedSession.getCameraConfig().getCameraId();
// Create an ARCore session that supports camera sharing.
sharedSession
= Session(this, EnumSet.of(Session.Feature.SHARED_CAMERA))

// Store the ARCore shared camera reference.
sharedCamera
= sharedSession.sharedCamera

// Store the ID of the camera that ARCore uses.
cameraId
= sharedSession.cameraConfig.cameraId

(Opcional) Informar o ARCore sobre qualquer superfície personalizada

Solicitar plataformas personalizadas adicionais aumenta as demandas de desempenho do dispositivo. Para garantir um bom desempenho, teste o app nos dispositivos em que o que os usuários utilizarão.

O ARCore solicitará dois fluxos por padrão:

  1. 1x fluxo de CPU YUV, atualmente sempre 640x480.
    O ARCore usa esse stream para rastreamento de movimento.
  2. Um stream de GPU 1x, normalmente 1920x1080
    Usar Session#getCameraConfig() para determinar a resolução de stream da GPU atual.

Para alterar a resolução do stream da GPU em dispositivos compatíveis, use getSupportedCameraConfigs() e setCameraConfig()

Como um indicador aproximado, você pode esperar:

Tipo de dispositivo Compatível com streams simultâneos
Telefones de última geração
  • Dois streams de CPU YUV, por exemplo: 640x480 e 1920x1080
  • 1x stream de GPU, por exemplo: 1920x1080
  • 1 imagem estática de alta resolução ocasional (JPEG), por exemplo, 12MP
Telefones de nível médio
  • Dois streams de CPU YUV, por exemplo: 640x480 e 1920x1080
  • 1x stream de GPU, por exemplo: 1920x1080
. –ou–
  • 1x fluxos de CPU YUV, por exemplo: 640x480 –ou– 1920x1080
  • 1x stream de GPU, por exemplo: 1920x1080
  • 1 imagem estática de alta resolução ocasional (JPEG), por exemplo, 12MP

Para usar superfícies personalizadas, como uma superfície de leitor de imagem da CPU, adicione-as à lista de plataformas que precisam ser atualizadas Por exemplo, um ImageReader.

sharedCamera.setAppSurfaces(this.cameraId, Arrays.asList(imageReader.getSurface()));
sharedCamera.setAppSurfaces(this.cameraId, listOf(imageReader.surface))

Abrir a câmera

Abra a câmera usando um callback encapsulado pelo ARCore:

// Wrap the callback in a shared camera callback.
CameraDevice.StateCallback wrappedCallback =
    sharedCamera
.createARDeviceStateCallback(cameraDeviceCallback, backgroundHandler);

// Store a reference to the camera system service.
cameraManager
= (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);

// Open the camera device using the ARCore wrapped callback.
cameraManager
.openCamera(cameraId, wrappedCallback, backgroundHandler);
// Wrap the callback in a shared camera callback.
val wrappedCallback = sharedCamera.createARDeviceStateCallback(cameraDeviceCallback, backgroundHandler)

// Store a reference to the camera system service.
val cameraManager = this.getSystemService(Context.CAMERA_SERVICE) as CameraManager

// Open the camera device using the ARCore wrapped callback.
cameraManager
.openCamera(cameraId, wrappedCallback, backgroundHandler)

Usar o callback de estado do dispositivo da câmera

No callback de estado do dispositivo da câmera, armazene uma referência a ele. iniciar uma nova sessão de captura.

public void onOpened(@NonNull CameraDevice cameraDevice) {
   
Log.d(TAG, "Camera device ID " + cameraDevice.getId() + " opened.");
   
SharedCameraActivity.this.cameraDevice = cameraDevice;
    createCameraPreviewSession
();
}
fun onOpened(cameraDevice: CameraDevice) {
 
Log.d(TAG, "Camera device ID " + cameraDevice.id + " opened.")
 
this.cameraDevice = cameraDevice
  createCameraPreviewSession
()
}

Criar uma nova sessão de captura

Crie uma nova solicitação de captura. Usar TEMPLATE_RECORD para garantir que a solicitação de captura seja compatível com o ARCore e permitir alternando entre os modos RA e não RA no momento da execução.

void createCameraPreviewSession() {
 
try {
   
// Create an ARCore-compatible capture request using `TEMPLATE_RECORD`.
    previewCaptureRequestBuilder
=
        cameraDevice
.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);

   
// Build a list of surfaces, starting with ARCore provided surfaces.
   
List<Surface> surfaceList = sharedCamera.getArCoreSurfaces();

   
// (Optional) Add a CPU image reader surface.
    surfaceList
.add(cpuImageReader.getSurface());

   
// The list should now contain three surfaces:
   
// 0. sharedCamera.getSurfaceTexture()
   
// 1. …
   
// 2. cpuImageReader.getSurface()

   
// Add ARCore surfaces and CPU image surface targets.
   
for (Surface surface : surfaceList) {
      previewCaptureRequestBuilder
.addTarget(surface);
   
}

   
// Wrap our callback in a shared camera callback.
   
CameraCaptureSession.StateCallback wrappedCallback =
        sharedCamera
.createARSessionStateCallback(cameraSessionStateCallback, backgroundHandler);

   
// Create a camera capture session for camera preview using an ARCore wrapped callback.
    cameraDevice
.createCaptureSession(surfaceList, wrappedCallback, backgroundHandler);
 
} catch (CameraAccessException e) {
   
Log.e(TAG, "CameraAccessException", e);
 
}
}
fun createCameraPreviewSession() {
 
try {
   
// Create an ARCore-compatible capture request using `TEMPLATE_RECORD`.
    previewCaptureRequestBuilder
= cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)

   
// Build a list of surfaces, starting with ARCore provided surfaces.
   
val surfaceList: MutableList<Surface> = sharedCamera.arCoreSurfaces

   
// (Optional) Add a CPU image reader surface.
    surfaceList
.add(cpuImageReader.getSurface())

   
// The list should now contain three surfaces:
   
// 0. sharedCamera.getSurfaceTexture()
   
// 1. …
   
// 2. cpuImageReader.getSurface()

   
// Add ARCore surfaces and CPU image surface targets.
   
for (surface in surfaceList) {
      previewCaptureRequestBuilder
.addTarget(surface)
   
}

   
// Wrap the callback in a shared camera callback.
   
val wrappedCallback = sharedCamera.createARSessionStateCallback(cameraSessionStateCallback, backgroundHandler)

   
// Create a camera capture session for camera preview using an ARCore wrapped callback.
    cameraDevice
.createCaptureSession(surfaceList, wrappedCallback, backgroundHandler)
 
} catch (e: CameraAccessException) {
   
Log.e(TAG, "CameraAccessException", e)
 
}
}

Iniciar no modo RA ou que não seja de RA

Para começar a capturar frames, chame captureSession.setRepeatingRequest(). do callback de estado onConfigured() da sessão de captura da câmera. Retome a sessão do ARCore no callback onActive() para iniciar no modo RA.

// Repeating camera capture session state callback.
CameraCaptureSession.StateCallback cameraSessionStateCallback =
   
new CameraCaptureSession.StateCallback() {

     
// Called when ARCore first configures the camera capture session after
     
// initializing the app, and again each time the activity resumes.
     
@Override
     
public void onConfigured(@NonNull CameraCaptureSession session) {
        captureSession
= session;
        setRepeatingCaptureRequest
();
     
}

     
@Override
     
public void onActive(@NonNull CameraCaptureSession session) {
       
if (arMode && !arcoreActive) {
          resumeARCore
();
       
}
     
}
   
};

// A repeating camera capture session capture callback.
CameraCaptureSession.CaptureCallback cameraCaptureCallback =
   
new CameraCaptureSession.CaptureCallback() {
     
@Override
     
public void onCaptureCompleted(…) {
        shouldUpdateSurfaceTexture
.set(true);
     
}
   
};

void setRepeatingCaptureRequest() {
    captureSession
.setRepeatingRequest(
        previewCaptureRequestBuilder
.build(), cameraCaptureCallback, backgroundHandler);
}

void resumeARCore() {
   
// Resume ARCore.
    sharedSession
.resume();
    arcoreActive
= true;

   
// Set the capture session callback while in AR mode.
    sharedCamera
.setCaptureCallback(cameraCaptureCallback, backgroundHandler);
}
val cameraSessionStateCallback = object : CameraCaptureSession.StateCallback() {
     
// Called when ARCore first configures the camera capture session after
     
// initializing the app, and again each time the activity resumes.
 
override fun onConfigured(session: CameraCaptureSession) {
    captureSession
= session
    setRepeatingCaptureRequest
()
 
}

 
override fun onActive(session: CameraCaptureSession) {
   
if (arMode && !arcoreActive) {
      resumeARCore
()
   
}
 
}
}

val cameraCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
 
override fun onCaptureCompleted(
    session
: CameraCaptureSession,
    request
: CaptureRequest,
    result
: TotalCaptureResult
 
) {
    shouldUpdateSurfaceTexture
.set(true);
 
}
}

fun setRepeatingCaptureRequest() {
  captureSession
.setRepeatingRequest(
    previewCaptureRequestBuilder
.build(), cameraCaptureCallback, backgroundHandler
 
)
}

fun resumeARCore() {
   
// Resume ARCore.
    sharedSession
.resume()
    arcoreActive
= true

   
// Set the capture session callback while in AR mode.
    sharedCamera
.setCaptureCallback(cameraCaptureCallback, backgroundHandler)
}

Alterne facilmente entre os modos RA e não RA durante a execução

Para alternar do modo não RA para o modo RA e retomar uma sessão pausada do ARCore:

// Resume the ARCore session.
resumeARCore
();
// Resume the ARCore session.
resumeARCore
()

Para mudar do modo RA para o modo não RA:

// Pause ARCore.
sharedSession
.pause();

// Create the Camera2 repeating capture request.
setRepeatingCaptureRequest
();
// Pause ARCore.
sharedSession
.pause()

// Create the Camera2 repeating capture request.
setRepeatingCaptureRequest
()