applyDefaultParameter
一、摘要
本篇文章阐述初始化流程中需要设置的默认参数包括如下:
- DisplayOrientation
- PreviewSize
- PictureSize
- DefaultFocusParameters
- DefaultFlashParameters
二、伪代码
先把函数写出来
private void applyDefaultParameters(){
//1.apply方向
//2.apply previewSize
//3.apply pictureSize
//4.apply focus mode
//5.apply flash mode
}
2.1 方向
参考【Camera1】Camera1源码分析【Java层】【2.4.2】
,先贴上相关代码:
/**
* Calculate display orientation
https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
*/
private int calcDisplayOrientation() {
CameraInfo info = new Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else {
// back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
分析:
- 这里我们已经在openCamera里获取到mFacing的信息了,可以省区代码里的getCameraInfo逻辑。
- 在某些兼容性手机上,方向可能有异常,需要做特殊的兼容逻辑处理
- setDisplayOrientation会抛异常,但是不是致命问题
因此可改造代码如下:
private int applyDefaultOrientation() {
int displayOrientation;
//1.问题机型兼容逻辑处理
if(CameraCompat.Camera1.isOrientationExcepDevice(Build.Model)){
displayOrientation = CameraCompat.Camera1.getOrientationForExcepDevices();
}else{
//2.逻辑计算
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
//mOrientation为 CameraInfo里 orientation字段,在openCamera阶段保存为变量即可
if (mFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
displayOrientation = (mOrientation + degrees) % 360;
displayOrientation = (360 - displayOrientation) % 360;
// compensate the mirror
} else {
// back-facing
displayOrientation = (mOrientation - degrees + 360) % 360;
}
}
try{
//3.非致命异常 try-catch
camera.setDisplayOrientation(displayOrientation);
}catch(Exception e){
Log.e(TAG, "setDisplayOrientation exception info = "+e.getInfo());
}
}
2.2 previewSize
Camera.java源码如下:
分析
- 如果camera is preview,需要 stop preview first,and then setPreviewSize
- 和displayOrientation有关系,正常后置和手机竖屏有90度的夹角。所以一般计算得到的最终displayOrientation = 90。 因此preview正常为如下:1920 * 1080; width > height。和手机分辨率(1080 * 1920) 有90度夹角
- 会抛异常
官方Demo如下
void adjustCameraParameters() {
SortedSet<Size> sizes = mPreviewSizes.sizes(mAspectRatio);
if (sizes == null) {
// Not supported
mAspectRatio = chooseAspectRatio();
sizes = mPreviewSizes.sizes(mAspectRatio);
}
Size size = chooseOptimalSize(sizes);
// Always re-apply camera parameters
// Largest picture size in this ratio
final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();
if (mShowingPreview) {
mCamera.stopPreview();
}
mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight());
mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
}
private AspectRatio chooseAspectRatio() {
AspectRatio r = null;
for (AspectRatio ratio : mPreviewSizes.ratios()) {
r = ratio;
if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) {
return ratio;
}
}
return r;
}
private Size chooseOptimalSize(SortedSet<Size> sizes) {
if (!mPreview.isReady()) {
// Not yet laid out
return sizes.first(); // Return the smallest size
}
int desiredWidth;
int desiredHeight;
final int surfaceWidth = mPreview.getWidth();
final int surfaceHeight = mPreview.getHeight();
if (isLandscape(mDisplayOrientation)) {
desiredWidth = surfaceHeight;
desiredHeight = surfaceWidth;
} else {
desiredWidth = surfaceWidth;
desiredHeight = surfaceHeight;
}
Size result = null;
for (Size size : sizes) {
// Iterate from small to large
if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
return size;
}
result = size;
}
return result;
}
分析:
- 通过mShowingPreview字段来判断是否stopPreview
- 通过aspectRatio来筛选
- 计算和aspectRatio匹配的分辨率
previewSize是相机支持的默认分辨率,每种分辨率有不同的宽高比。常见的有
- 1:1
- 2:3
- 3:4
- 9:16
具体的根据用户体验层面来进行设定。
对应的UI默认分辨率的宽位1080,因此如果不考虑指定可选的分辨率。默认分辨率为:
- 1080 * 1080(1:1)
- 1080 * 1920 (9:16)
- 1080 * 1440 (3:4)
- 1080 * 1620 (2:3)
在Camera1源码分析一文中。【4.2 参数内容】
里给出了preview-values值。如下:
preview-size-values -> 1920x1080,1600x960,1600x900,1600x736,1600x720,1440x1080,1440x720,1280x720,1080x1080,960x720,720x480,640x480,352x288,320x240,176x144
preview-size | aspectRatio |
---|---|
1920x1080 | 16:9 |
1600x960 | 5:3 |
1600x900 | 16:9 |
1600x736 | 50:23 |
1600x720 | 20:9 |
1440x1080 | 4:3 |
1440x720 | 2:1 |
1280x720 | 16:9 |
1080x1080 | 1:1 |
960x720 | 4:3 |
720x480 | 3:2 |
640x480 | 4:3 |
352x288 | 11:9 |
320x240 | 4:3 |
176x144 | 11:9 |
因此我们需要遍历preview size list。通过对比筛选,选择一个匹配的。如果都不匹配,选择最近匹配的最后设置在上面。
注意如下2个情况:
1.不设置preview-size camera会有默认设置,不影响功能使用
2. 设置的如果非supportPreviewSize会报错
Caused by: java.lang.RuntimeException: setParameters failed
at android.hardware.Camera.native_setParameters(Native Method)
at android.hardware.Camera.setParameters(Camera.java:2192)
at com.google.android.cameraview.Camera1.adjustCameraParameters(Camera1.java:330)
at com.google.android.cameraview.Camera1.openCamera(Camera1.java:295)
at com.google.android.cameraview.Camera1.start(Camera1.java:90)
at com.google.android.cameraview.CameraView.start(CameraView.java:245)
at com.google.android.cameraview.demo.MainActivity.onResume(MainActivity.java:131)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1453)
at android.app.Activity.performResume(Activity.java:8050)
最终,我们可更改代码如下:
public void applyPreviewSize(){
if (mShowingPreview) {
mCamera.stopPreview();
}
Camera.Parameters params = null;
try{
//mCamera.getParameters();需要try-catch住,不是致命bug
params = mCamera.getParameters();
}catch(Exception e){
...
}
if(params == null){
//不设置 return
return;
}
List<Size> previewSize = null;
try{
//可参看源码,内部没有异常try-catch。可能会抛异常,也可能返回为null;
previewSize = params.getSupportedPreviewSizes();
}catch(Exception e){
...
}
if(previewSize == null || previewSize.size() == 0){
return;
}
float min = Integer.MAX_VALUE;
Size selectSize = null;
for(Size s : previewSize){
float ar = AspectRatio.cal(s);
if(ar - mAspectRatio < min){
selectPreviewSize = s;
min = Math.min(min,Math.abs(mAspectRatio - ar));
}
}
if(selectPreviewSize == null){
return;
}
try{
params.setPreviewSize(selectSize.getWidth(),selectSize.getHeight());
}catch(Exception e){
...
}
}
2.3 pictureSize
pictureSize和previewSize是同样的逻辑,可参考2.2
。只需:
- getSupportedPreviewSizes -> getSupportedPictureSizes
- setPreviewSize -> setPictureSize
即可。
2.4 DefaultFocusParameters
官方代码如下:
private boolean setAutoFocusInternal(boolean autoFocus) {
mAutoFocus = autoFocus;
if (isCameraOpened()) {
final List<String> modes = mCameraParameters.getSupportedFocusModes();
if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
} else {
mCameraParameters.setFocusMode(modes.get(0));
}
return true;
} else {
return false;
}
}
具体到相机对焦细节,将在Camera1系列文章对焦里阐述,这里直接贴上相关代码:
public boolean applyDefaultFocusMode(){
if(!isCameraOpened()){
return false;
}
try{
final List<String> modes = mCameraParameters.getSupportedFocusModes();
if(modes == null || modes.size() == 0){
return false;
}
if (isCurVideoTab()
&& modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
} else {
mCameraParameters.setFocusMode(modes.get(0));
}
}catch(Exception e){
...
}
}
以上是伪代码mCameraParameters
,即可全局变量保存更新,也可以临时获取和更新但一定要注意:
try-catch
,读取和更新CameraParameters
都存在抛异常的可能性
2.5 DefaultFlashParameters
官方Demo如下:
public void applyDefaultFlashMode(){
private static final SparseArrayCompat<String> FLASH_MODES = new SparseArrayCompat<>();
static {
FLASH_MODES.put(Constants.FLASH_OFF, Camera.Parameters.FLASH_MODE_OFF);
FLASH_MODES.put(Constants.FLASH_ON, Camera.Parameters.FLASH_MODE_ON);
FLASH_MODES.put(Constants.FLASH_TORCH, Camera.Parameters.FLASH_MODE_TORCH);
FLASH_MODES.put(Constants.FLASH_AUTO, Camera.Parameters.FLASH_MODE_AUTO);
FLASH_MODES.put(Constants.FLASH_RED_EYE, Camera.Parameters.FLASH_MODE_RED_EYE);
}
private boolean setFlashInternal(int flash) {
if (isCameraOpened()) {
List<String> modes = mCameraParameters.getSupportedFlashModes();
String mode = FLASH_MODES.get(flash);
if (modes != null && modes.contains(mode)) {
mCameraParameters.setFlashMode(mode);
mFlash = flash;
return true;
}
String currentMode = FLASH_MODES.get(mFlash);
if (modes == null || !modes.contains(currentMode)) {
mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
mFlash = Constants.FLASH_OFF;
return true;
}
return false;
} else {
mFlash = flash;
return false;
}
}
}
这里可直接使用官方Demo的方法,也可以自己做另外更改,只需要处理好异常情况即可。
三、代码整理
最后再整理下代码如下:
private void applyDefaultParameters(){
//1.apply方向
applyDefaultOrientation();
//2.apply previewSize
applyDefaultPreviewSize();
//3.apply pictureSize
applyDefaultPictureSize();
//4.apply focus mode
applyDefaultFocusMode();
//5.apply flash mode
applyDefaultFlashMode();
}