Android
API de la cámara 2
Buscar..
Parámetros
Parámetro | Detalles |
---|---|
CameraCaptureSession | Una sesión de captura configurada para un CameraDevice , usado para capturar imágenes de la cámara o reprocesar imágenes capturadas desde la cámara en la misma sesión anterior |
CameraDevice | Una representación de una sola cámara conectada a un dispositivo Android |
CameraCharacteristics | Las propiedades que describen un CameraDevice. Estas propiedades son fijas para un CameraDevice determinado y se pueden consultar a través de la interfaz de CameraManager con getCameraCharacteristics(String) |
CameraManager | Un administrador de servicios del sistema para detectar, caracterizar y conectarse a CameraDevices . Puede obtener una instancia de esta clase llamando a Context.getSystemService() |
CaptureRequest | Un paquete inmutable de configuraciones y salidas necesarias para capturar una sola imagen desde el dispositivo de la cámara. Contiene la configuración del hardware de captura (sensor, lente, flash), el proceso de procesamiento, los algoritmos de control y los buffers de salida. También contiene la lista de Superficies de destino para enviar datos de imagen para esta captura. Puede crearse utilizando una instancia de CaptureRequest.Builder , obtenida llamando a createCaptureRequest(int) |
CaptureResult | El subconjunto de los resultados de una sola captura de imagen del sensor de imagen. Contiene un subconjunto de la configuración final para el hardware de captura (sensor, lente, flash), la tubería de procesamiento, los algoritmos de control y los buffers de salida. Es producido por un CameraDevice después de procesar una CaptureRequest |
Observaciones
- Las API de Camera2 están disponibles en API 21+ (Lollipop y más allá)
- Incluso si un dispositivo Android tiene una ROM 21+ oficialmente, no hay garantía de que implemente las API de Camera2, el fabricante tiene la responsabilidad de implementarlo o no (por ejemplo, LG G2 tiene soporte oficial de Lollipop, pero no tiene API de Camera2)
- Con Camera2, la cámara ("Camera1") está en desuso
- Con gran poder viene una gran responsabilidad: es más fácil estropearlo cuando se utilizan estas API.
- Recuerde, si solo desea tomar una foto en su aplicación, y simplemente obtenerla, no necesita implementar Camera2, puede abrir la aplicación de la cámara del dispositivo a través de un Intent y volver a recibirla.
Vista previa de la cámara principal en un TextureView
En este caso, compilando contra la API 23, los permisos también se manejan.
Debe agregar en el Manifiesto el siguiente permiso (donde sea que esté usando el nivel de API):
<uses-permission android:name="android.permission.CAMERA"/>
Estamos a punto de crear una actividad (Camera2Activity.java) que llena un TextureView
con la vista previa de la cámara del dispositivo.
La Actividad que vamos a usar es una AppCompatActivity típica:
public class Camera2Activity extends AppCompatActivity {
Atributos (Es posible que deba leer el ejemplo completo para comprenderlo)
El MAX_PREVIEW_SIZE
garantizado por Camera2 API es 1920x1080
private static final int MAX_PREVIEW_WIDTH = 1920;
private static final int MAX_PREVIEW_HEIGHT = 1080;
TextureView.SurfaceTextureListener
maneja varios eventos del ciclo de vida en un TextureView
. En este caso, estamos escuchando esos eventos. Cuando la SurfaceTexture está lista, inicializamos la cámara. Cuando cambia el tamaño, configuramos la vista previa que viene de la cámara en consecuencia
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
Un CameraDevice
representa la cámara de un dispositivo físico. En este atributo, guardamos el ID del CameraDevice
actual
private String mCameraId;
Esta es la vista ( TextureView
) que TextureView
para "dibujar" la vista previa de la cámara
private TextureView mTextureView;
La CameraCaptureSession
para la vista previa de la cámara
private CameraCaptureSession mCaptureSession;
Una referencia al CameraDevice
abierto CameraDevice
private CameraDevice mCameraDevice;
El Size
de la vista previa de la cámara.
private Size mPreviewSize;
CameraDevice.StateCallback
se llama cuando CameraDevice
cambia su estado
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
finish();
}
};
Un hilo adicional para ejecutar tareas que no deberían bloquear la interfaz de usuario
private HandlerThread mBackgroundThread;
Un Handler
para ejecutar tareas en segundo plano
private Handler mBackgroundHandler;
Un ImageReader
que maneja la captura de imágenes fijas
private ImageReader mImageReader;
CaptureRequest.Builder
para la vista previa de la cámara
private CaptureRequest.Builder mPreviewRequestBuilder;
CaptureRequest
generado por mPreviewRequestBuilder
private CaptureRequest mPreviewRequest;
Un Semaphore
para evitar que la aplicación salga antes de cerrar la cámara.
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
ID constante de la solicitud de permiso
private static final int REQUEST_CAMERA_PERMISSION = 1;
Métodos de ciclo de vida de Android
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
mTextureView = (TextureView) findViewById(R.id.texture);
}
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
public void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
Camera2 métodos relacionados
Esos son métodos que utilizan las API de Camera2
private void openCamera(int width, int height) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestCameraPermission();
return;
}
setUpCameraOutputs(width, height);
configureTransform(width, height);
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
Cierra la cámara actual.
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
mCameraOpenCloseLock.release();
}
}
Configura variables miembro relacionadas con la cámara.
private void setUpCameraOutputs(int width, int height) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(cameraId);
// We don't use a front facing camera in this sample.
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// For still image captures, we use the largest available size.
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea());
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
mImageReader.setOnImageAvailableListener(
null, mBackgroundHandler);
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
// Danger! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest);
mCameraId = cameraId;
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
Toast.makeText(Camera2Activity.this, "Camera2 API not supported on this device", Toast.LENGTH_LONG).show();
}
}
Crea una nueva CameraCaptureSession
para la vista previa de la cámara
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == mCameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
try {
// Auto focus should be continuous for camera preview.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,
null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("Failed");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
Métodos relacionados con permisos para Android API 23+
private void requestCameraPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
new AlertDialog.Builder(Camera2Activity.this)
.setMessage("R string request permission")
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(Camera2Activity.this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.create();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(Camera2Activity.this, "ERROR: Camera permissions not granted", Toast.LENGTH_LONG).show();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
Hilos de fondo / métodos de manejo
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Métodos de utilidad
Dadas las opciones de Size
admitidas por una cámara, elija la más pequeña que sea al menos tan grande como el tamaño de la vista de textura respectiva, y que sea tan grande como el tamaño máximo respectivo, y cuya relación de aspecto coincida con el valor especificado. Si no existe, elija el más grande que sea a lo sumo tan grande como el tamaño máximo respectivo, y cuya relación de aspecto coincida con el valor especificado
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e("Camera2", "Couldn't find any suitable preview size");
return choices[0];
}
}
Este método configura la transformación Matrix
necesaria para mTextureView
private void configureTransform(int viewWidth, int viewHeight) {
if (null == mTextureView || null == mPreviewSize) {
return;
}
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
mTextureView.setTransform(matrix);
}
Este método compara dos Size
basados en sus áreas.
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
no hay mucho que ver aqui
/**
* Shows a {@link Toast} on the UI thread.
*
* @param text The message to show
*/
private void showToast(final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show();
}
});
}