【发布时间】:2013-07-14 23:26:07
【问题描述】:
我的相机应用在屏幕上显示相机预览,并在后台处理它。以下是相关代码,尽可能精简(例如,不显示错误处理或字段声明):
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;
}
}
在所有设备上,相机预览都能正常显示,并且在大多数设备(模拟器、三星 Galaxy S3 等)上,mBuffer 中存储的数据也是正确的(当然是在 NV21 到 RGB 转换和旋转之后)。但是,许多设备在 onPreviewFrame 中提供的数据不正确。我确定数据在收到后会正确转换为 RGB,因此问题似乎出在提供给mBuffer 的原始数据中。我注意到 this bug report 与 YV12(别名 YUV420p)相机预览格式有关,但我使用的是旧默认值 NV21(别名 YUV420sp),必须根据 compatibility standard 支持它(参见 7.5.3.2,第 29 页的底部)。
例如,对于这个场景(此处显示在三星 Galaxy Tab 2 上的相机预览中):
在选项卡 2 上传递给 mBuffer 的数据如下所示:
在摩托罗拉 Droid 4 上看起来像:
在所有设备上获取 Android 相机预览数据的正确方法是什么?
编辑: 对于processFrame(),我使用 OpenCV 转换为 RGB 并旋转。请参阅this answer 和this answer。
【问题讨论】:
-
如何显示/处理图像?
-
@EJTH 我使用 OpenCV(方法
cvtColor带有标志COLOR_YUV420sp2RGBA)。 -
所以我猜你是在不是安卓设备的设备上执行到 RGB 的转换?出于好奇,
YuvImage.compressToJpeg()是否会产生类似的扭曲图像? -
@EJTH
YuvImage.compressToJpeg()确实有效,这一事实使我得到了正确的答案。将此放入答案中,我将奖励它。