В моем приложении камеры отображается предварительный просмотр камеры на экране, а также обрабатывается в фоновом режиме. Вот соответствующий код, максимально сжатый (например, без обработки ошибок или показаний полей):
public final class CameraView extends SurfaceView implements
SurfaceHolder.Callback, Runnable, PreviewCallback {
public CameraView(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
void openCamera() {
// Called from parent activity after setting content view to CameraView
mCamera = Camera.open();
mCamera.setPreviewCallbackWithBuffer(this);
}
public void surfaceCreated(SurfaceHolder holder) {
new Thread(this).start();
// Set CameraView to the optimal camera preview size
final Camera.Parameters params = mCamera.getParameters();
final List<Camera.Size> sizes = params.getSupportedPreviewSizes();
final int screenWidth = ((View) getParent()).getWidth();
int minDiff = Integer.MAX_VALUE;
Camera.Size bestSize = null;
if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// Find the camera preview width that best matches the
// width of the surface.
for (Camera.Size size : sizes) {
final int diff = Math.abs(size.width - screenWidth);
if (diff < minDiff) {
minDiff = diff;
bestSize = size;
}
}
} else {
// Find the camera preview HEIGHT that best matches the
// width of the surface, since the camera preview is rotated.
mCamera.setDisplayOrientation(90);
for (Camera.Size size : sizes) {
final int diff = Math.abs(size.height - screenWidth);
if (Math.abs(size.height - screenWidth) < minDiff) {
minDiff = diff;
bestSize = size;
}
}
}
final int previewWidth = bestSize.width;
final int previewHeight = bestSize.height;
ViewGroup.LayoutParams layoutParams = getLayoutParams();
layoutParams.height = previewHeight;
layoutParams.width = previewWidth;
setLayoutParams(layoutParams);
params.setPreviewFormat(ImageFormat.NV21);
mCamera.setParameters(params);
int size = previewWidth * previewHeight *
ImageFormat.getBitsPerPixel(params.getPreviewFormat()) / 8;
mBuffer = new byte[size];
mCamera.addCallbackBuffer(mBuffer);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
public void onPreviewFrame(byte[] data, Camera camera) {
CameraView.this.notify();
}
public void run() {
mThreadRun = true;
while (mThreadRun) {
synchronized (this) {
this.wait();
processFrame(mBuffer); // convert to RGB and rotate - not shown
}
// Request a new frame from the camera by putting
// the buffer back into the queue
mCamera.addCallbackBuffer(mBuffer);
}
mHolder.removeCallback(this);
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
public void surfaceDestroyed(SurfaceHolder holder) {
mThreadRun = false;
}
}
На всех устройствах предварительный просмотр камеры отображается правильно, а на большинстве (эмулятор, Samsung Galaxy S3 и т.д.) данные, хранящиеся в mBuffer
, также правильны (после преобразования NV21 в RGB и вращения, конечно). Тем не менее, несколько устройств не предоставляют правильные данные в onPreviewFrame. Я уверен, что данные будут правильно преобразованы в RGB после их получения, поэтому проблема заключается в исходных данных, переданных в mBuffer
. Я заметил этот отчет об ошибках, относящийся к формату предварительного просмотра камеры YV12 (alias YUV420p), но я использую старый default, NV21 ( псевдоним YUV420sp), который должен поддерживаться в соответствии с стандартом совместимости (см. 7.5.3.2, внизу страницы 29).
Например, для этой сцены (показано здесь в Camera Preview на вкладке Samsung Galaxy Tab 2):
данные, переданные в mBuffer
на вкладке 2, выглядят следующим образом:
и на Motorola Droid 4 выглядит так:
Каков правильный способ получить данные предварительного просмотра камеры Android на всех устройствах?
Изменить: для processFrame()
, я использовал OpenCV для преобразования в RGB и вращения. См. этот ответ и этот ответ.