Camera1初始化流程
一、摘要
本篇文章包括如下2部分:
- Part1:官方代码初始化流程分析
- Part2:官方Demo的不足,以及如何改进到专业产品
本篇文章可结合Camera1综述和Camera1源码分析一同参看。
二、Camera1 Demo分析
代码 => google/cameraview
完整的代码可参考如上github
项目中的Camera1.java
,也可参考文章最后附录部分
。本小节后续文章,将以如下的顺序进行代码拆解和分析。
Camera1初始化模块流程 |
---|
变量解析 |
构造函数 |
start |
初始化配置 |
2.1 变量解析
变量 | 说明 |
---|---|
INVALID_CAMERA_ID | 无效CameraId常量 |
FLASH_MODES | flashMode见Camera1源码分析 【#4.2】 flash_mode参数 |
mCameraId | cameraId,见Camera1源码分析 【#2.4】 |
isPictureCaptureInProgress | 是否正在Capture |
mCamera | Camera实例类 |
mCameraParameters | 参数:见Camera1源码分析 【#4】 |
mCameraInfo | Camera信息,见Camera1源码分析 【#2.4】 |
mPreviewSizes | 预览分辨率,见Camera1源码分析 【#4.2】 preview-size参数 |
mPictureSizes | 拍照分辨率,见Camera1源码分析 【#4.2】 picture-size参数 |
mAspectRatio | 画幅大小,宽高比 |
mShowingPreview | 是否显示Preview |
mAutoFocus | 是否自动对焦 |
mFacing | 是否前后置 |
mFlash | 闪光灯模式 |
mDisplayOrientation | 设备display方向 |
2.2 构造函数
Camera1(Callback callback, PreviewImpl preview) {
super(callback, preview);
preview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged() {
if (mCamera != null) {
setUpPreview();
adjustCameraParameters();
}
}
});
}
可承接Camera预览区域的UI组件有如下几种:
- SurfaceView
- TextureView
- GLSurfaceView
具体每个View的介绍这里就不赘述了,自行查阅相关资料。当这3个View变更【available、size update and etc】的时候会触发onSurfaceChanged
函数。在构造函数里的回掉函数会触发调用如下2个函数:
- Fun1:
setUpPreview()
- Fun2:
adjustCameraParameters()
setUpPreview()
绑定Camera展示的View
// Suppresses Camera#setPreviewTexture
@SuppressLint("NewApi")
void setUpPreview() {
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
} else {
mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
adjustCameraParameters()
见【#2.4】
2.3 Start
@Override
boolean start() {
chooseCamera();
openCamera();
if (mPreview.isReady()) {
setUpPreview();
}
mShowingPreview = true;
mCamera.startPreview();
return true;
}
start里包含了3个部分
- chooseCamera():选择打开的CameraId
- openCamera():链接打开相机
- mCamera.startPreview():预览
2.3.1 chooseCamera
/**
* This rewrites {@link #mCameraId} and {@link #mCameraInfo}.
*/
private void chooseCamera() {
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, mCameraInfo);
if (mCameraInfo.facing == mFacing) {
mCameraId = i;
return;
}
}
mCameraId = INVALID_CAMERA_ID;
}
遍历所有可得CameraId,选择mCameraInfo.facing和mFacing一致的摄像头打开。
2.3.2 openCamera
private void openCamera() {
1.注意,在open之前要先check一遍,并且releaseCamera
***************************************
if (mCamera != null) {
releaseCamera();
}
2.Camera.open -> native_setup方法
***************************************
mCamera = Camera.open(mCameraId);
3.见Camera1源码分析【#4】`
***************************************
mCameraParameters = mCamera.getParameters();
// Supported preview sizes
mPreviewSizes.clear();
4.见Camera1源码分析【#4.2】preview-size-values和picture-values
***************************************
for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
mPreviewSizes.add(new Size(size.width, size.height));
}
// Supported picture sizes;
mPictureSizes.clear();
for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
mPictureSizes.add(new Size(size.width, size.height));
}
// AspectRatio
if (mAspectRatio == null) {
mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;
}
4.见本文2.4
***************************************
adjustCameraParameters();
5.见Camera1源码分析【#4.2】orientation
***************************************
mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));
mCallback.onCameraOpened();
}
2.4 参数设置
void adjustCameraParameters() {
SortedSet<Size> sizes = mPreviewSizes.sizes(mAspectRatio);
if (sizes == null) { // Not supported
mAspectRatio = chooseAspectRatio();
sizes = mPreviewSizes.sizes(mAspectRatio);
}
1.根据宽高比,选择合适的分辨率
***************************************
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();
}
2.设置预览尺寸、拍照尺寸、预览和拍照方向
***************************************
mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight());
mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
3.闪光灯和Flash默认设置。
***************************************
setAutoFocusInternal(mAutoFocus);
setFlashInternal(mFlash);
mCamera.setParameters(mCameraParameters);
if (mShowingPreview) {
mCamera.startPreview();
}
}
三、More Works
在第二部分拆解分析了Camera1.java的初始化流程,更进一步,还需要更多的工作。
- 线程调度
- 选取合适的画幅
- 几种Preview View设置方式
- Preview CallBack
- 性能统计
- 错误统计
- 各种异常容错处理【重试、兜底逻辑】
- 拍照声音的兼容
- 时序控制
- 方向兼容
结合Demo和More Work,将在Camera1初始化流程(下)给出如何开发一个健壮性高的Camera1初始化。
附录1 Camera1.java类源码
class Camera1 extends CameraViewImpl {
private static final int INVALID_CAMERA_ID = -1;
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 int mCameraId;
private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false);
Camera mCamera;
private Camera.Parameters mCameraParameters;
private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
private final SizeMap mPreviewSizes = new SizeMap();
private final SizeMap mPictureSizes = new SizeMap();
private AspectRatio mAspectRatio;
private boolean mShowingPreview;
private boolean mAutoFocus;
private int mFacing;
private int mFlash;
private int mDisplayOrientation;
Camera1(Callback callback, PreviewImpl preview) {
super(callback, preview);
preview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged() {
if (mCamera != null) {
setUpPreview();
adjustCameraParameters();
}
}
});
}
@Override
boolean start() {
chooseCamera();
openCamera();
if (mPreview.isReady()) {
setUpPreview();
}
mShowingPreview = true;
mCamera.startPreview();
return true;
}
@Override
void stop() {
if (mCamera != null) {
mCamera.stopPreview();
}
mShowingPreview = false;
releaseCamera();
}
// Suppresses Camera#setPreviewTexture
@SuppressLint("NewApi")
void setUpPreview() {
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
} else {
mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
boolean isCameraOpened() {
return mCamera != null;
}
@Override
void setFacing(int facing) {
if (mFacing == facing) {
return;
}
mFacing = facing;
if (isCameraOpened()) {
stop();
start();
}
}
@Override
int getFacing() {
return mFacing;
}
@Override
Set<AspectRatio> getSupportedAspectRatios() {
SizeMap idealAspectRatios = mPreviewSizes;
for (AspectRatio aspectRatio : idealAspectRatios.ratios()) {
if (mPictureSizes.sizes(aspectRatio) == null) {
idealAspectRatios.remove(aspectRatio);
}
}
return idealAspectRatios.ratios();
}
@Override
boolean setAspectRatio(AspectRatio ratio) {
if (mAspectRatio == null || !isCameraOpened()) {
// Handle this later when camera is opened
mAspectRatio = ratio;
return true;
} else if (!mAspectRatio.equals(ratio)) {
final Set<Size> sizes = mPreviewSizes.sizes(ratio);
if (sizes == null) {
throw new UnsupportedOperationException(ratio + " is not supported");
} else {
mAspectRatio = ratio;
adjustCameraParameters();
return true;
}
}
return false;
}
@Override
AspectRatio getAspectRatio() {
return mAspectRatio;
}
@Override
void setAutoFocus(boolean autoFocus) {
if (mAutoFocus == autoFocus) {
return;
}
if (setAutoFocusInternal(autoFocus)) {
mCamera.setParameters(mCameraParameters);
}
}
@Override
boolean getAutoFocus() {
if (!isCameraOpened()) {
return mAutoFocus;
}
String focusMode = mCameraParameters.getFocusMode();
return focusMode != null && focusMode.contains("continuous");
}
@Override
void setFlash(int flash) {
if (flash == mFlash) {
return;
}
if (setFlashInternal(flash)) {
mCamera.setParameters(mCameraParameters);
}
}
@Override
int getFlash() {
return mFlash;
}
@Override
void takePicture() {
if (!isCameraOpened()) {
throw new IllegalStateException(
"Camera is not ready. Call start() before takePicture().");
}
if (getAutoFocus()) {
mCamera.cancelAutoFocus();
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
takePictureInternal();
}
});
} else {
takePictureInternal();
}
}
void takePictureInternal() {
if (!isPictureCaptureInProgress.getAndSet(true)) {
final long start = System.currentTimeMillis();
mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.i("sun_q","time cost = "+(System.currentTimeMillis() - start));
isPictureCaptureInProgress.set(false);
mCallback.onPictureTaken(data);
camera.cancelAutoFocus();
camera.startPreview();
}
});
}
}
@Override
void setDisplayOrientation(int displayOrientation) {
if (mDisplayOrientation == displayOrientation) {
return;
}
mDisplayOrientation = displayOrientation;
if (isCameraOpened()) {
mCameraParameters.setRotation(calcCameraRotation(displayOrientation));
mCamera.setParameters(mCameraParameters);
mCamera.setDisplayOrientation(calcDisplayOrientation(displayOrientation));
}
}
/**
* This rewrites {@link #mCameraId} and {@link #mCameraInfo}.
*/
private void chooseCamera() {
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, mCameraInfo);
if (mCameraInfo.facing == mFacing) {
mCameraId = i;
return;
}
}
mCameraId = INVALID_CAMERA_ID;
}
private void openCamera() {
if (mCamera != null) {
releaseCamera();
}
mCamera = Camera.open(mCameraId);
mCameraParameters = mCamera.getParameters();
// Supported preview sizes
mPreviewSizes.clear();
for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
mPreviewSizes.add(new Size(size.width, size.height));
}
// Supported picture sizes;
mPictureSizes.clear();
for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
mPictureSizes.add(new Size(size.width, size.height));
}
// AspectRatio
if (mAspectRatio == null) {
mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;
}
adjustCameraParameters();
mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));
mCallback.onCameraOpened();
}
private AspectRatio chooseAspectRatio() {
AspectRatio r = null;
for (AspectRatio ratio : mPreviewSizes.ratios()) {
r = ratio;
if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) {
return ratio;
}
}
return r;
}
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());
mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
setAutoFocusInternal(mAutoFocus);
setFlashInternal(mFlash);
mCamera.setParameters(mCameraParameters);
if (mShowingPreview) {
mCamera.startPreview();
}
}
@SuppressWarnings("SuspiciousNameCombination")
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;
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
mCallback.onCameraClosed();
}
}
/**
* Calculate display orientation
* https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
*
* This calculation is used for orienting the preview
*
* Note: This is not the same calculation as the camera rotation
*
* @param screenOrientationDegrees Screen orientation in degrees
* @return Number of degrees required to rotate preview
*/
private int calcDisplayOrientation(int screenOrientationDegrees) {
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360;
} else { // back-facing
return (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360;
}
}
/**
* Calculate camera rotation
*
* This calculation is applied to the output JPEG either via Exif Orientation tag
* or by actually transforming the bitmap. (Determined by vendor camera API implementation)
*
* Note: This is not the same calculation as the display orientation
*
* @param screenOrientationDegrees Screen orientation in degrees
* @return Number of degrees to rotate image in order for it to view correctly.
*/
private int calcCameraRotation(int screenOrientationDegrees) {
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return (mCameraInfo.orientation + screenOrientationDegrees) % 360;
} else { // back-facing
final int landscapeFlip = isLandscape(screenOrientationDegrees) ? 180 : 0;
return (mCameraInfo.orientation + screenOrientationDegrees + landscapeFlip) % 360;
}
}
/**
* Test if the supplied orientation is in landscape.
*
* @param orientationDegrees Orientation in degrees (0,90,180,270)
* @return True if in landscape, false if portrait
*/
private boolean isLandscape(int orientationDegrees) {
return (orientationDegrees == Constants.LANDSCAPE_90 ||
orientationDegrees == Constants.LANDSCAPE_270);
}
/**
* @return {@code true} if {@link #mCameraParameters} was modified.
*/
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;
}
}
/**
* @return {@code true} if {@link #mCameraParameters} was modified.
*/
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;
}
}
}