我的相机应用程序在屏幕上显示相机预览,并在后台处理它。以下是相关代码,尽可能地压缩(例如,没有显示错误处理或字段声明):
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
的原始数据中。我注意到有关YV12(别名YUV420p)相机预览格式的this bug report,但我使用的是旧默认值NV21(别名YUV420sp),必须根据compatibility standard支持(见7.5.3.2,第29页底部。)
例如,对于此场景(此处显示在Samsung Galaxy Tab 2上的Camera Preview中):
在Tab 2上传递给mBuffer
的数据如下所示:
并且在摩托罗拉Droid 4上看起来像:
在所有设备上获取Android相机预览数据的正确方法是什么?
对于processFrame()
,编辑,我使用OpenCV转换为RGB并旋转。请参阅this answer和this answer。
答案 0 :(得分:8)
唯一的问题是我没有设置预览宽度和高度:
params.setPreviewSize(previewWidth, previewHeight);
mCamera.setParameters(params);
这意味着我为数组分配的高度和宽度(与previewWidth * previewHeight成比例)往往比返回的实际数据大小(与默认预览成比例)宽度和预览高度)。在某些手机上,默认设置与previewWidth和previewHeight的大小相同,因此没有问题。
答案 1 :(得分:3)
你也可以尝试这个
public void takeSnapPhoto() {
camera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Parameters parameters = camera.getParameters();
int format = parameters.getPreviewFormat();
//YUV formats require more conversion
if (format == ImageFormat.NV21 || format == ImageFormat.YUY2 || format == ImageFormat.NV16) {
int w = parameters.getPreviewSize().width;
int h = parameters.getPreviewSize().height;
// Get the YuV image
YuvImage yuv_image = new YuvImage(data, format, w, h, null);
// Convert YuV to Jpeg
Rect rect = new Rect(0, 0, w, h);
ByteArrayOutputStream output_stream = new ByteArrayOutputStream();
yuv_image.compressToJpeg(rect, 100, output_stream);
byte[] byt = output_stream.toByteArray();
FileOutputStream outStream = null;
try {
// Write to SD Card
File file = createFileInSDCard(FOLDER_PATH, "Image_"+System.currentTimeMillis()+".jpg");
//Uri uriSavedImage = Uri.fromFile(file);
outStream = new FileOutputStream(file);
outStream.write(byt);
outStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
});}
答案 2 :(得分:2)
从严格依赖预览数据的Barcode Scanner开始工作,我觉得我已经看到了太阳下的每一个虫子。我的建议只是不要调用setPreviewFormat()
并让它使用默认值。幸运的是,默认值是你想要的。似乎有更少的设备无法获得默认设置,而不是将呼叫提升到setPreviewFormat()
的设备。尝试至少,可能是也可能不是。