Camera1Impl类之OpenCamera
一、摘要
本篇文章阐述实现Camera1初始化的方法。会对照官方demo的代码模版进行针对性修改。
二、Camera1Impl初始化实现
public void initCamera(int cameraId, float aspectRatio){
}
2.1 Open函数:
在Camera1源码分析中,最后的方法调用都会走到Native层面。并且根据Camera1综述文章中,在底层会去链接实际的相机设备,获取相机的信息。
Camera1
java层源码上层提供了open函数如下,具体open函数执行流程可参考Camera1源码分析 【3.1】
。open函数会返回一个Camera.java的实例对象,并且有如下几个override函数:
public static Camera open() {
int numberOfCameras = getNumberOfCameras();
CameraInfo cameraInfo = new CameraInfo();
for (int i = 0; i < numberOfCameras; i++) {
getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
return new Camera(i);
}
}
return null;
}
/* @param cameraId the hardware camera to access, between 0 and
* {@link #getNumberOfCameras()}-1.
* @return a new Camera object, connected, locked and ready for use.
* @throws RuntimeException if opening the camera fails (for example, if the
* camera is in use by another process or device policy manager has
* disabled the camera).
* @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
*/
public static Camera open(int cameraId) {
return new Camera(cameraId);
}
- open方法没有传入回掉函数,是同步调用执行的
- open方法会返回
null
对象 - getCameraInfo会抛异常出错
- open会默认打开后置摄像头
- open有个重载函数支持传入CameraId,会
throw exception and eturn a new Camera object, connected, locked and ready for use.
结论:考虑到cameraId需要UI层上层控制,一般就会选者支持cameraId参数传入的方法。
2.2 锁同步
综合如下几个原因需要使用锁同步
open
函数对于上层是同步调用- 应用切换前后置,切换画幅,切换前后台都会触发
initCamera
方法调用 - 底层链接Camera,获取Camera信息是在另一个进程中
在initCamera
里添加如下代码:
public void initCamera(int cameraId, float aspectRatio){
synchronized(lock){
if(acquireLock(timeout = 2000)){
Log.i(TAG, "initCamera超时")
}
}
}
频繁暴力调用initCamera会通过锁申请来同步化。这里超时后不会直接return
。直接return
会导致功能不可用。具体的锁实现和锁超时机制这里就不赘述了,各有各的实现方法。
2.3 chooseCameraId
官方Demo中的逻辑如下:
@Override
boolean start() {
chooseCamera();
openCamera();
if (mPreview.isReady()) {
setUpPreview();
}
mShowingPreview = true;
mCamera.startPreview();
return true;
}
/**
* 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;
}
分析:
官方Demo通过
mFacing
来控制初始打开哪个Camera设备,我们可通过传入的cameraId
参数来初始化对应Camera设备
在Camera理论协议和规范文章中,所有的手机需要支持前置和后置摄像。但也存在一些特殊情况:如摄像头坏了,某些手机兼容确实不支持前置或后置。在遍历
getNumberOfCameras
返无效ID
时应给予用户提示做容错处理
Camera.getCameraInfo(i, mCameraInfo);
API调用会抛异常该异常通常和camera设备状态、时序错乱、声音audio系统都可能有关系。但是该功能异常并不会是致命的Bug
。
更新代码如下:
public void initCamera(int cameraId, float aspectRatio){
synchronized(lock){
if(acquireLock(timeout = 2000)){
Log.i(TAG, "initCamera超时")
}
int count = 0;
// @return total number of accessible camera devices,
//or 0 if there are no cameras or an error was encountered enumerating them.
try{
count = Camera.getNumberOfCameras();
}catch(Exception e){
count = -1;
}
if(count == 0){
Toast("清检查设备前后置摄像头")
return;
}
}
}
通过调用Camera.getNumberOfCameras();
方法,如果返回0则表示当前无可用Camera设备。如果返回camera = -1则表示获取信息发生异常,更新代码如下:
try{
count = Camera.getNumberOfCameras();
}catch(Exception e){
count = -1;
}
if(count == 0){
Toast("清检查设备前后置摄像头")
//释放锁
releaseLock();
return;
}
if(count == -1){
releaseLock();
if(retryCount> DEFAULT_RETRY_COUNT){
retryInitCamera();
retryCount++;
}
return;
}
retryCount.= 0;
当count == -1时,做一个简单的重试逻辑。但是由于瞬间立马调用重试可能获取到的CameraDevice信息还是错误,因此稍微通过CameraHandler
做一定时间的延迟,可更新代码如下:
if(count == -1){
releaseLock();
if(retryCount> DEFAULT_RETRY_COUNT){
getCameraHandle().postDelay(()->{
retryInitCamera();
retryCount++;
},20);
}
return;
}
retryCount= 0;
2.4 openCamera();
CameraId是用0开始排列的,0为后置,1为前置。因此通过源码mCameraId = i
看。是直接把序号赋值给mCameraId
。我们直接调用open(cameraId)方法,可继续更新代码如下:
mcamera = Camera.open(cameraId);
通过源码我们可以看到如下注释:
You must call {@link #release()} when you are done using the camera,
* otherwise it will remain locked and be unavailable to other applications.
继续修改代码如下:
if(mCamera!=null){
//releaseCamera释放camera,并set null to mCamera。
releaseCamera();
}
mcamera = Camera.open(cameraId);
由于某些手机没有后置摄像头,或者前置、后置摄像头返回null
。Camera.open(cameraId);
可能会返回null
对象。添加容错代码如下:
if(mCamera!=null){
//releaseCamera释放camera,并set null to mCamera。
releaseCamera();
}
mCamera = Camera.open(cameraId);
if(mCamera == null){
Toast("打开摄像头失败");
releaseLock();
return;
}
2.5 getCameraInfo
如果之前都顺利,那么我们已经打开了特定cameraId相机设备。那么接下来就是读取设备信息:getCameraInfo
源码如下,并且会抛异常。
/**
* Returns the information about a particular camera.
* If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
*
* @throws RuntimeException if an invalid ID is provided, or if there is an
* error retrieving the information (generally due to a hardware or other
* low-level failure).
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
_getCameraInfo(cameraId, cameraInfo);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
IAudioService audioService = IAudioService.Stub.asInterface(b);
try {
if (audioService.isCameraSoundForced()) {
// Only set this when sound is forced; otherwise let native code
// decide.
cameraInfo.canDisableShutterSound = false;
}
} catch (RemoteException e) {
Log.e(TAG, "Audio service is unavailable for queries");
}
}
继续添加代码:
mCamera = Camera.open(cameraId);
if(mCamera == null){
Toast("打开摄像头失败");
releaseLock();
return;
}
mCameraId = cameraId;
Camera.CameraInfo info = new Camera.CameraInfo();
try{
Camera.getCameraInfo(mCameraId,info)
}catch(Exception e){
Log.e(TAG,"exception info = "+ e.getInfo());
}
mCanDisableSound = info.canDisableShutterSound;
if(mCanDisableSound){
try {
// 关闭快门声
mCamera.enableShutterSound(false);
}catch (Exception e){
}
}else{
//TODO 需要做额外的关闭声音容错处理
}
同样enableShutterSound
会抛异常,但不是致命问题,需要try-catch
/* @throws RuntimeException if the call fails; usually this would be because
* of a hardware or other low-level error, or because release() has been
* called on this Camera instance.
*/
public final boolean enableShutterSound(boolean enabled)
如果mCanDisableSound = false
则需要在拍照时做额外的处理逻辑,将在Camera1拍照逻辑中阐述。到这里我们总算链接成功了Camera设备,结下来需要做的就是applyDefaultParameter();
和startPreview()
两个部分。
三、伪代码整理
这里我们整理伪代码如下:
public void initCamera(int cameraId, float aspectRatio){
synchronized(lock){
try{
if(acquireLock(timeout = 2000)){
Log.i(TAG, "initCamera超时")
}
openCamera(cameraId);
applyDefaultParameters();
startPreview();
releaseLock();
Log.i(TAG, "initCamera succ")
retryCount = 0;
}catch(Exception e){
releaseLock();
Log.e(TAG, "initCamera exception info = "+e.getInfo();
if(retryCount > DEFAULT_RETRY_COUNT){
getCameraHandler().postDelay(()->{
retryCount++;
initCamera(cameraId, aspectRatio);
},20);
}
}
}
}
- 把initCamera()拆分成三个逻辑部分
- 整体
try - catch
容错并添加exception 重试逻辑
- 锁同步时序
public void openCamera(int cameraId){
int count = 0;
// @return total number of accessible camera devices,
//or 0 if there are no cameras or an error was encountered enumerating them.
try{
count = Camera.getNumberOfCameras();
}catch(Exception e){
count = -1;
}
if(count == 0){
Toast("清检查设备前后置摄像头")
//释放锁
releaseLock();
return;
}
if(count == -1){
releaseLock();
if(retryCount> DEFAULT_RETRY_COUNT){
getCameraHandle().postDelay(()->{
openCamera(cameraId);
retryCount++;
},20);
}
return;
}
retryCount= 0;
if(mCamera!=null){
//releaseCamera释放camera,并set null to mCamera。
releaseCamera();
}
mCamera = Camera.open(cameraId);
if(mCamera == null){
Toast("打开摄像头失败");
releaseLock();
return;
}
mCameraId = cameraId;
Camera.CameraInfo info = new Camera.CameraInfo();
try{
Camera.getCameraInfo(mCameraId,info)
}catch(Exception e){
Log.e(TAG,"exception info = "+ e.getInfo());
}
mCanDisableSound = info.canDisableShutterSound;
if(mCanDisableSound){
try {
// 关闭快门声
mCamera.enableShutterSound(false);
}catch (Exception e){
}
}else{
//TODO 需要做额外的关闭声音容错处理
}
}
下一篇讲阐述applyDefaultFocus()逻辑