上一篇文章主要是掌握ZXing解码总体的步骤,关于细节方面的代码就一笔带过了,本篇文章将会深刻细节,更详细的讲解有关相机配置方面的知识。java
直接看代码,找到调用相机初始化配置的代码,上篇文章已经分析了在CaptureActivity
中怎么调到initCamera
方法的,这里再次看下这个方法的代码,以下git
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalStateException("No SurfaceHolder provided");
}
//相机已经打开
if (cameraManager.isOpen()) {
Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
return;
}
try {
//打开相机并初始化硬件参数
cameraManager.openDriver(surfaceHolder);
// 实例化一个handler并开始预览.
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
}
decodeOrStoreSavedBitmap(null, null);
} catch (IOException ioe) {
Log.w(TAG, ioe);
displayFrameworkBugMessageAndExit();
} catch (RuntimeException e) {
// Barcode Scanner has seen crashes in the wild of this variety:
// java.?lang.?RuntimeException: Fail to connect to camera service
Log.w(TAG, "Unexpected error initializing camera", e);
displayFrameworkBugMessageAndExit();
}
}
复制代码
上篇文章分析到这句代码cameraManager.openDriver(surfaceHolder);
就直接说了这句代码的做用,并无进入openDriver
方法详细的看代码,这里看下openDriver
中的代码,以下github
public synchronized void openDriver(SurfaceHolder holder) throws IOException {
OpenCamera theCamera = camera;
if (theCamera == null) {
//更具requestedCameraId打开对应的摄像头
theCamera = OpenCameraInterface.open(requestedCameraId);
if (theCamera == null) {
throw new IOException("Camera.open() failed to return object from driver");
}
camera = theCamera;
}
//是否已经初始化,没有初始化则进行初始化
if (!initialized) {
initialized = true;
configManager.initFromCameraParameters(theCamera);//分析一
if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
requestedFramingRectWidth = 0;
requestedFramingRectHeight = 0;
}
}
Camera cameraObject = theCamera.getCamera();
Camera.Parameters parameters = cameraObject.getParameters();
String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
try {
configManager.setDesiredCameraParameters(theCamera, false);
} catch (RuntimeException re) {
// Driver failed
Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened);
// Reset:
if (parametersFlattened != null) {
parameters = cameraObject.getParameters();
parameters.unflatten(parametersFlattened);
try {
cameraObject.setParameters(parameters);
configManager.setDesiredCameraParameters(theCamera, true);
} catch (RuntimeException re2) {
// Well, darn. Give up
Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
}
}
}
cameraObject.setPreviewDisplay(holder);
}
复制代码
这里重点看下“分析一”initFromCameraParameters
方法中的代码,以下app
void initFromCameraParameters(OpenCamera camera) {
Camera.Parameters parameters = camera.getCamera().getParameters();
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//获取WindowManager默认的Display
Display display = manager.getDefaultDisplay();
//屏幕的旋转角度
int displayRotation = display.getRotation();
int cwRotationFromNaturalToDisplay;
switch (displayRotation) {
case Surface.ROTATION_0:
cwRotationFromNaturalToDisplay = 0;
break;
case Surface.ROTATION_90:
cwRotationFromNaturalToDisplay = 90;
break;
case Surface.ROTATION_180:
cwRotationFromNaturalToDisplay = 180;
break;
case Surface.ROTATION_270:
cwRotationFromNaturalToDisplay = 270;
break;
default:
// Have seen this return incorrect values like -90
if (displayRotation % 90 == 0) {
cwRotationFromNaturalToDisplay = (360 + displayRotation) % 360;
} else {
throw new IllegalArgumentException("Bad rotation: " + displayRotation);
}
}
Log.i(TAG, "Display at: " + cwRotationFromNaturalToDisplay);
int cwRotationFromNaturalToCamera = camera.getOrientation();
Log.i(TAG, "Camera at: " + cwRotationFromNaturalToCamera);
// Still not 100% sure about this. But acts like we need to flip this:
if (camera.getFacing() == CameraFacing.FRONT) {
cwRotationFromNaturalToCamera = (360 - cwRotationFromNaturalToCamera) % 360;
Log.i(TAG, "Front camera overriden to: " + cwRotationFromNaturalToCamera);
}
/* SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String overrideRotationString; if (camera.getFacing() == CameraFacing.FRONT) { overrideRotationString = prefs.getString(PreferencesActivity.KEY_FORCE_CAMERA_ORIENTATION_FRONT, null); } else { overrideRotationString = prefs.getString(PreferencesActivity.KEY_FORCE_CAMERA_ORIENTATION, null); } if (overrideRotationString != null && !"-".equals(overrideRotationString)) { Log.i(TAG, "Overriding camera manually to " + overrideRotationString); cwRotationFromNaturalToCamera = Integer.parseInt(overrideRotationString); } */
cwRotationFromDisplayToCamera =
(360 + cwRotationFromNaturalToCamera - cwRotationFromNaturalToDisplay) % 360;
Log.i(TAG, "Final display orientation: " + cwRotationFromDisplayToCamera);
if (camera.getFacing() == CameraFacing.FRONT) {
Log.i(TAG, "Compensating rotation for front camera");
cwNeededRotation = (360 - cwRotationFromDisplayToCamera) % 360;
} else {
cwNeededRotation = cwRotationFromDisplayToCamera;
}
Log.i(TAG, "Clockwise rotation from display to camera: " + cwNeededRotation);
Point theScreenResolution = new Point();
display.getSize(theScreenResolution);
screenResolution = theScreenResolution;
Log.i(TAG, "Screen resolution in current orientation: " + screenResolution);
cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
Log.i(TAG, "Camera resolution: " + cameraResolution);
bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
Log.i(TAG, "Best available preview size: " + bestPreviewSize);
boolean isScreenPortrait = screenResolution.x < screenResolution.y;
boolean isPreviewSizePortrait = bestPreviewSize.x > bestPreviewSize.y;
if (isScreenPortrait == isPreviewSizePortrait) {
previewSizeOnScreen = bestPreviewSize;
} else {
previewSizeOnScreen = new Point(bestPreviewSize.y, bestPreviewSize.x);
}
Log.i(TAG, "Preview size on screen: " + previewSizeOnScreen);
}
复制代码
虽然这个方法代码有点多,可是由于这个方法是用来相机初始配置的,因此,要详细的分析一下,首先看下这部分的代码ide
Camera.Parameters parameters = camera.getCamera().getParameters();
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//获取WindowManager默认的Display
Display display = manager.getDefaultDisplay();
//屏幕的旋转角度
int displayRotation = display.getRotation();
int cwRotationFromNaturalToDisplay;
switch (displayRotation) {
case Surface.ROTATION_0:
cwRotationFromNaturalToDisplay = 0;
break;
case Surface.ROTATION_90:
cwRotationFromNaturalToDisplay = 90;
break;
case Surface.ROTATION_180:
cwRotationFromNaturalToDisplay = 180;
break;
case Surface.ROTATION_270:
cwRotationFromNaturalToDisplay = 270;
break;
default:
// Have seen this return incorrect values like -90
if (displayRotation % 90 == 0) {
cwRotationFromNaturalToDisplay = (360 + displayRotation) % 360;
} else {
throw new IllegalArgumentException("Bad rotation: " + displayRotation);
}
}
Log.i(TAG, "Display at: " + cwRotationFromNaturalToDisplay);
int cwRotationFromNaturalToCamera = camera.getOrientation();
Log.i(TAG, "Camera at: " + cwRotationFromNaturalToCamera);
// Still not 100% sure about this. But acts like we need to flip this:
if (camera.getFacing() == CameraFacing.FRONT) {
cwRotationFromNaturalToCamera = (360 - cwRotationFromNaturalToCamera) % 360;
Log.i(TAG, "Front camera overriden to: " + cwRotationFromNaturalToCamera);
}
cwRotationFromDisplayToCamera =
(360 + cwRotationFromNaturalToCamera - cwRotationFromNaturalToDisplay) % 360;
Log.i(TAG, "Final display orientation: " + cwRotationFromDisplayToCamera);
if (camera.getFacing() == CameraFacing.FRONT) {
Log.i(TAG, "Compensating rotation for front camera");
cwNeededRotation = (360 - cwRotationFromDisplayToCamera) % 360;
} else {
cwNeededRotation = cwRotationFromDisplayToCamera;
}
复制代码
相信没有相机开发经验的同窗,看到这段代码会一脸懵逼,不要紧,咱们一步步来,在理解这段代码前,须要咱们掌握下面的一些概念。post
强烈建议你们先看下这篇文章 Android: Camera相机开发详解(上) —— 知识储备,相信看过以后,你就会理解上面的代码了,其实,上面代码的做用就是设置相机采集图片的预览方向,就是不管手机是横屏仍是竖屏,你看到的图像都是与手机方向一致的。优化
文章前部分,已经分析了ZXing设置预览方向的代码,可是只设置预览方向仍是不够的,还要根据屏幕的宽高比来找到相机采集图片最合适的预览尺寸,不然就会出现相机预览图拉伸变形的问题。 继续看initFromCameraParameters
方法中的代码,以下ui
Point theScreenResolution = new Point();
display.getSize(theScreenResolution);
screenResolution = theScreenResolution;
Log.i(TAG, "Screen resolution in current orientation: " + screenResolution);
cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
Log.i(TAG, "Camera resolution: " + cameraResolution);
bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
Log.i(TAG, "Best available preview size: " + bestPreviewSize);
boolean isScreenPortrait = screenResolution.x < screenResolution.y;
boolean isPreviewSizePortrait = bestPreviewSize.x > bestPreviewSize.y;
if (isScreenPortrait == isPreviewSizePortrait) {
previewSizeOnScreen = bestPreviewSize;
} else {
previewSizeOnScreen = new Point(bestPreviewSize.y, bestPreviewSize.x);
}
复制代码
上面代码中的screenResolution
变量是屏幕分辨率,从这个变量中能够分别获取屏幕宽高的像素值。咱们来重点看下这两句代码this
cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
复制代码
上面的一句代码是获取相机的最佳分辨率,下面的一句代码是获取获取相机的最佳预览尺寸。如今来看下是怎么获取最佳尺寸的,findBestPreviewSizeValue
方法的代码以下idea
public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {
//获取相机支持的尺寸,手机不一样会有不一样的值
List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
if (rawSupportedSizes == null) {
Log.w(TAG, "Device returned no supported preview sizes; using default");
Camera.Size defaultSize = parameters.getPreviewSize();
if (defaultSize == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
return new Point(defaultSize.width, defaultSize.height);
}
if (Log.isLoggable(TAG, Log.INFO)) {
StringBuilder previewSizesString = new StringBuilder();
for (Camera.Size size : rawSupportedSizes) {
previewSizesString.append(size.width).append('x').append(size.height).append(' ');
}
Log.i(TAG, "Supported preview sizes: " + previewSizesString);
}
//这句代码是获取屏幕宽高的比例
double screenAspectRatio = screenResolution.x / (double) screenResolution.y;
// Find a suitable size, with max resolution
int maxResolution = 0;
Camera.Size maxResPreviewSize = null;
//for循环的做用是找到相机合适的尺寸和最大的分辨率,这里
//合适的尺寸指的是和屏幕宽高比相同的尺寸。
for (Camera.Size size : rawSupportedSizes) {
int realWidth = size.width;
int realHeight = size.height;
int resolution = realWidth * realHeight;
if (resolution < MIN_PREVIEW_PIXELS) {
continue;
}
boolean isCandidatePortrait = realWidth < realHeight;
int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;
double distortion = Math.abs(aspectRatio - screenAspectRatio);
if (distortion > MAX_ASPECT_DISTORTION) {
continue;
}
//这句代码是找到与屏幕宽高比一致的尺寸,不然就用相机默认的尺寸
if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
Point exactPoint = new Point(realWidth, realHeight);
Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
return exactPoint;
}
// Resolution is suitable; record the one with max resolution
if (resolution > maxResolution) {
maxResolution = resolution;
maxResPreviewSize = size;
}
}
// If no exact match, use largest preview size. This was not a great idea on older devices because
// of the additional computation needed. We're likely to get here on newer Android 4+ devices, where
// the CPU is much more powerful.
if (maxResPreviewSize != null) {
Point largestSize = new Point(maxResPreviewSize.width, maxResPreviewSize.height);
Log.i(TAG, "Using largest suitable preview size: " + largestSize);
return largestSize;
}
// If there is nothing at all suitable, return current preview size
Camera.Size defaultPreview = parameters.getPreviewSize();
if (defaultPreview == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
return defaultSize;
}
复制代码
从上面代码中的注释能够看到这里存在一些小问题,上面代码的逻辑是有与屏幕像素比例相同的相机尺寸才返回,不然就用相机默认的尺寸,相机默认的尺寸可能与屏幕的尺寸比有较大的差距,这样就会出现预览图像变形的问题。
这里能够将代码优化为,返回最接近屏幕宽高比的相机尺寸。这里的优化将会在后面的文章中进行详细的讲解。
上面的代码是将一些变量的值设置好,最终,配置相机的参数在CameraConfigurationManager
类中的setDesiredCameraParameters
中,这里就不详细分析了。
这里没有处理采集的照片,采集到的照片数据仍是横屏的,以下
onPreviewFrame
中改变代码,更改后的代码以下
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();
Handler thePreviewHandler = previewHandler;
if (cameraResolution != null && thePreviewHandler != null) {
Point screenResolution = configManager.getScreenResolution();
Message message;
if (screenResolution.x < screenResolution.y){
// 手机为竖屏时
message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.y,
cameraResolution.x, data);
} else {
// 手机为横屏时
message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
cameraResolution.y, data);
}
message.sendToTarget();
previewHandler = null;
} else {
Log.d(TAG, "Got preview callback, but no handler or resolution available");
}
复制代码
解释:手机竖屏时,相机传感器采集的数据为横屏的数据,为了与竖屏相对应,须要将相机采集的图片宽高互换,这里只是互换了宽高,可是采集的数据宽高并无转换,所以还须要将数据的宽高转换。
代码以下
//将原始图像传感器的数据转换为竖屏
if (width < height) {
// portrait
byte[] rotatedData = new byte[data.length];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++)
rotatedData[y * width + width - x - 1] = data[y + x * height];
}
data = rotatedData;
}
复制代码
将上面的代码,加入到DecodeHandler
类中的decode
方法开头便可。
虽然,这时已经将相机采集的横屏数据转化为竖屏的了,可是,工做尚未完成,还须要设置获取二维码的区域,设置的方法是CameraManager类中的getFramingRectInPrevie方法。这里我就补贴具体的代码了,你们根据前文的内容和本身的思考来修改里面的代码。
文章主要分析了相机配置的代码,选择拍摄图像的最佳尺寸及处理相机采集到的数据,重点是要理解相机的数据采集与图像预览的设置。本篇修改的代码在这里。
本文已由公众号“AndroidShared”首发