Android Camera2, снимайте постоянно
Мне нужно фотографировать постоянно с Camera2 API. Он отлично работает на устройствах высокого класса (например, Nexus 5X), но на более медленных (например, Samsung Galaxy A3) предварительный просмотр зависает.
Код немного длинный, поэтому я публикую только самые важные части:
Метод вызван, чтобы начать мой предварительный просмотр:
private void startPreview() {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
if(texture != null) {
try {
// 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) {
// If the camera is already closed, return:
if (mCameraDevice == null) { return; }
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
// Auto focus should be continuous for camera preview.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mPreviewRequest = mPreviewRequestBuilder.build();
// Start the preview
try { mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mPreviewBackgroundHandler); }
catch (CameraAccessException e) { e.printStackTrace(); }
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e(TAG, "Configure failed");
}
}, null
);
}
catch (CameraAccessException e) { e.printStackTrace(); }
}
}
Метод, призванный сделать снимок:
private void takePicture() {
try {
CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
mCaptureSession.capture(captureBuilder.build(), null, mCaptureBackgroundHandler);
}
catch (CameraAccessException e) { e.printStackTrace(); }
}
А вот и мой ImageReader:
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(final ImageReader reader) {
mSaveBackgroundHandler.post(new Runnable() {
@Override
public void run() {
// Set the destination file:
File destination = new File(getExternalFilesDir(null), "image_" + mNumberOfImages + ".jpg");
mNumberOfImages++;
// Acquire the latest image:
Image image = reader.acquireLatestImage();
// Save the image:
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
FileOutputStream output = null;
try {
output = new FileOutputStream(destination);
output.write(bytes);
}
catch (IOException e) { e.printStackTrace(); }
finally {
image.close();
if (null != output) {
try { output.close(); }
catch (IOException e) { e.printStackTrace(); }
}
}
// Take a new picture if needed:
if(mIsTakingPictures) {
takePicture();
}
}
});
}
};
У меня есть кнопка, которая переключает mIsTakingPictures логическое значение, и делает первый вызов takePicture.
Напомним, я использую 3 темы:
- один для предварительного просмотра
- один для захвата
- один для сохранения изображения
Что может быть причиной этого замораживания?
2 ответа
Невозможно избежать потери кадров при предварительном просмотре, когда вы постоянно снимаете изображения на слабых устройствах. Единственный способ избежать этого - на устройствах, которые поддерживают TEMPLATE_ZERO_SHUTTER_LAG
и используя reprocessableCaptureSession
, Документация об этом довольно ужасна, и найти способ реализовать ее может быть одиссея. У меня есть эта проблема несколько месяцев назад, и, наконец, я нашел способ ее реализации:
Как использовать reprocessCaptureRequest с API-интерфейсом camera2
В этом ответе вы также можете найти некоторые тесты Google CTS, которые также реализуют ReprocessableCaptureSession и снимать некоторые серийные снимки с помощью шаблона ZSL.
Наконец, вы также можете использовать CaptureBuilder
с вашей поверхностью предварительного просмотра и прикрепленной поверхностью считывателя изображений, в этом случае ваш предварительный просмотр будет продолжать работать все время, а также вы будете сохранять каждый кадр как новое изображение. Но у вас все равно будет проблема заморозки.
Я также попытался реализовать захват пакета, используя обработчик, который отправляет новый capture
вызывать каждые 100 миллисекунд, эта вторая опция была довольно хорошей по производительности и позволяла избежать потери частоты кадров, но вы не получите столько захватов в секунду, сколько два ImageReader
вариант.
Надеюсь, что мой ответ вам немного поможет, API 2 все еще немного сложен, и здесь не так много примеров или информации об этом.
Одна вещь, которую я заметил на младших устройствах: предварительный просмотр останавливается после захвата, даже при использовании камеры 1 api, поэтому его необходимо перезапустить вручную, что приводит к небольшому замораживанию при захвате изображения с высоким разрешением.
Но камера 2 api предоставляет возможность получить необработанное изображение при съемке (это было невозможно на устройствах, которые у меня есть при использовании камеры 1 (Huawei P7, Sony Xperia E5, wiko UFeel)). Использование этой функции намного быстрее, чем захват JPEG (возможно, из-за сжатия JPEG), поэтому предварительный просмотр можно перезапустить раньше, а предварительный просмотр будет короче. Конечно, используя это решение, вам придется конвертировать изображение из YUV в JPEG в фоновом режиме.