问题描述:
之前在机顶盒上面对接视频会议APK时,发现第三方应用调用Camera.open()无法打开通过usb外接的摄像头。
定位分析:
我们通过阅读Android Framework中Camera.java(具体路径为frameworks\base\core\java\android\hardware\)文件不难发现问题,代码如下:
/**
* Creates a new Camera object to access the first back-facing camera on the
* device. If the device does not have a back-facing camera, this returns
* null.
* @see #open(int)
*/
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;
}
直接调用Camera.open()时,系统会去读取所有的摄像头,然后优先返回CAMERA_FACING_BACK这样一个属性的这个摄像头,这个是屏幕后面的摄像头,具体定义如下:
/**
* The facing of the camera is opposite to that of the screen.
*/
public static final int CAMERA_FACING_BACK = 0;
/**
* The facing of the camera is the same as that of the screen.
*/
public static final int CAMERA_FACING_FRONT = 1;
由于机顶盒和手机并不一样,没有所谓的后置摄像头,所以第三方应用直接就获取到的是null。
解决方案如下:
1.应用在调用摄像头时使用另外的接口如下:
/**
* Creates a new Camera object to access a particular hardware camera. If
* the same camera is opened by other applications, this will throw a
* RuntimeException.
*
* You must call {@link #release()} when you are done using the camera,
* otherwise it will remain locked and be unavailable to other applications.
*
*
Your application should only have one Camera object active at a time
* for a particular hardware camera.
*
*
Callbacks from other methods are delivered to the event loop of the
* thread which called open(). If this thread has no event loop, then
* callbacks are delivered to the main application event loop. If there
* is no main application event loop, callbacks are not delivered.
*
*
Caution: On some devices, this method may
* take a long time to complete. It is best to call this method from a
* worker thread (possibly using {@link android.os.AsyncTask}) to avoid
* blocking the main application UI thread.
*
* @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);
}
在这里直接带上参数0,当然前提是系统已经识别到了摄像头,这样系统就会打开外接的唯一的摄像头。
2.修改Framework层代码适配第三方要求如下:
/**
* Creates a new Camera object to access the first back-facing camera on the
* device. If the device does not have a back-facing camera, this returns
* null.
* @see #open(int)
*/
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(0);
//}
}
return null;
}
在这里修改后是在调用Camera.open()时直接返回第一个摄像头而不去判断是否是后置摄像头,这里需要特别注意的是getNumberOfCameras()这个接口这一行千万不能注释掉,否则摄像头在开机第一次才能调用成功,后续拔插摄像头,Camera.open()都会无法正常获取到摄像头设备,因为这个接口涉及到摄像头设备更新的流程。
扩展:
之前有疑问过为什么注释掉getNumberOfCameras()这个接口后,接入设备后开机第一次可以正常获取,但是拔插设备后无法正常获取。后来根据这个查询不难发现这个是一个native方法:
/**
* Returns the number of physical cameras available on this device.
*/
public native static int getNumberOfCameras();
在这里我就直接跳过JNI层了,直接到CameraService.cpp里面去了,路径为frameworks\av\services\camera\libcameraservice\,我们通过查找不难发现getNumberOfCameras()对应的实现地方:
int32_t CameraService::getNumberOfCameras() {
mNumberOfCameras = mModule->get_number_of_cameras();
for (int i = 0; i < mNumberOfCameras; i++) {
setCameraFree(i);
}
return mNumberOfCameras;
}
从代码中我们可以看出,实际上又是调用了其他的接口 mModule->get_number_of_cameras(),其实我们在CameraService.cpp中应该可以发现有两处调用这个接口,另外一处如下:
void CameraService::onFirstRef()
{
LOG1("CameraService::onFirstRef");
BnCameraService::onFirstRef();
if (hw_get_module(CAMERA_HARDWARE_MODULE_ID,
(const hw_module_t **)&mModule) < 0) {
ALOGE("Could not load camera HAL module");
mNumberOfCameras = 0;
}
else {
ALOGI("Loaded \"%s\" camera module", mModule->common.name);
mNumberOfCameras = mModule->get_number_of_cameras();
if (mNumberOfCameras > MAX_CAMERAS) {
ALOGE("Number of cameras(%d) > MAX_CAMERAS(%d).",
mNumberOfCameras, MAX_CAMERAS);
mNumberOfCameras = MAX_CAMERAS;
}
for (int i = 0; i < mNumberOfCameras; i++) {
setCameraFree(i);
}
if (mModule->common.module_api_version >=
CAMERA_MODULE_API_VERSION_2_1) {
mModule->set_callbacks(this);
}
CameraDeviceFactory::registerService(this);
}
}
其实从上面的命名不难看出,这个是在开机第一次起来后CameraService服务起来就会运行的,那么get_number_of_cameras()里面又做了些什么呢,继续跟进应该是到Hal层,最后跟到了CameraModule.cpp这里,由于是Hisilicon平台的,所以路径为device\hisilicon\bigfish\hardware\camera\camera_hal\,各个厂家可能不同,这里就不多说这个了,代码如下:
/*
**************************************************************************
* FunctionName: camera_get_number_of_cameras;
* Description : NA;
* Input : NA;
* Output : NA;
* ReturnValue : NA;
* Other : NA;
**************************************************************************
*/
int camera_get_number_of_cameras(void)
{
CAMERA_HAL_LOGV("enter %s()", __FUNCTION__);
int CameraNum = 0;
struct stat nBuf;
char dev[LEN_OF_DEVICE_NAME]= {0};
for(int i = 0; i < MAX_CAMERA_SENSORS; i++)
{
snprintf(dev, sizeof(dev), "/dev/video%d", i);
if(stat(dev, &nBuf) == 0)
{
CameraNum++;
}
}
CAMERA_HAL_LOGI("camera_get_number_of_cameras = %d", CameraNum);
return CameraNum;
}
从代码不难发现,Hisilicon这部分是直接从设备节点来读取摄像头数量的。
以上就是getNumberOfCameras()的实现流程了,也许在这个流程中看不到为什么注释掉后,Camera.open()无法正常打印相机,其实不然,我们继续分析,我们修改后实际上使用了Camera.open(0),我们通过查看open(0)这个接口如下:
Camera(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
mPreviewCallback = null;
mPostviewCallback = null;
mUsingPreviewAllocation = false;
mZoomListener = null;
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
String packageName = ActivityThread.currentPackageName();
native_setup(new WeakReference(this), cameraId, packageName);
}
在这里我们只需要关注native_setup(new WeakReference(this), cameraId, packageName)这个方法,基本上可以确定这是个native方法:
private native final void native_setup(Object camera_this, int cameraId,
String packageName);
找到对应的JNI层代码如下:
// connect to camera service
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
jobject weak_this, jint cameraId, jstring clientPackageName)
{
// Convert jstring to String16
const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL);
jsize rawClientNameLen = env->GetStringLength(clientPackageName);
String16 clientName(rawClientName, rawClientNameLen);
env->ReleaseStringChars(clientPackageName, rawClientName);
sp camera = Camera::connect(cameraId, clientName,
Camera::USE_CALLING_UID);
if (camera == NULL) {
jniThrowRuntimeException(env, "Fail to connect to camera service");
return;
}
// make sure camera hardware is alive
if (camera->getStatus() != NO_ERROR) {
jniThrowRuntimeException(env, "Camera initialization failed");
return;
}
jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
jniThrowRuntimeException(env, "Can't find android/hardware/Camera");
return;
}
// We use a weak reference so the Camera object can be garbage collected.
// The reference is only used as a proxy for callbacks.
sp context = new JNICameraContext(env, weak_this, clazz, camera);
context->incStrong((void*)android_hardware_Camera_native_setup);
camera->setListener(context);
// save context in opaque field
env->SetIntField(thiz, fields.context, (int)context.get());
}
同样的这里我只需要关注 Camera::connect(cameraId, clientName,Camera::USE_CALLING_UID),这里cameraId就是我们修改后的0,从jni再到CameraService.cpp,找到对应代码如下:
status_t CameraService::connect(
const sp& cameraClient,
int cameraId,
const String16& clientPackageName,
int clientUid,
/*out*/
sp& device) {
String8 clientName8(clientPackageName);
int callingPid = getCallingPid();
LOG1("CameraService::connect E (pid %d \"%s\", id %d)", callingPid,
clientName8.string(), cameraId);
status_t status = validateConnect(cameraId, /*inout*/clientUid);
if (status != OK) {
return status;
}
sp client;
{
Mutex::Autolock lock(mServiceLock);
sp clientTmp;
if (!canConnectUnsafe(cameraId, clientPackageName,
cameraClient->asBinder(),
/*out*/clientTmp)) {
return -EBUSY;
} else if (client.get() != NULL) {
device = static_cast(clientTmp.get());
return OK;
}
int facing = -1;
int deviceVersion = getDeviceVersion(cameraId, &facing);
// If there are other non-exclusive users of the camera,
// this will tear them down before we can reuse the camera
if (isValidCameraId(cameraId)) {
// transition from PRESENT -> NOT_AVAILABLE
updateStatus(ICameraServiceListener::STATUS_NOT_AVAILABLE,
cameraId);
}
switch(deviceVersion) {
case CAMERA_DEVICE_API_VERSION_1_0:
client = new CameraClient(this, cameraClient,
clientPackageName, cameraId,
facing, callingPid, clientUid, getpid());
break;
case CAMERA_DEVICE_API_VERSION_2_0:
case CAMERA_DEVICE_API_VERSION_2_1:
case CAMERA_DEVICE_API_VERSION_3_0:
client = new Camera2Client(this, cameraClient,
clientPackageName, cameraId,
facing, callingPid, clientUid, getpid(),
deviceVersion);
break;
case -1:
ALOGE("Invalid camera id %d", cameraId);
return BAD_VALUE;
default:
ALOGE("Unknown camera device HAL version: %d", deviceVersion);
return INVALID_OPERATION;
}
status_t status = connectFinishUnsafe(client, client->getRemote());
if (status != OK) {
// this is probably not recoverable.. maybe the client can try again
// OK: we can only get here if we were originally in PRESENT state
updateStatus(ICameraServiceListener::STATUS_PRESENT, cameraId);
return status;
}
mClient[cameraId] = client;
LOG1("CameraService::connect X (id %d, this pid is %d)", cameraId,
getpid());
}
// important: release the mutex here so the client can call back
// into the service from its destructor (can be at the end of the call)
device = client;
return OK;
}
上述代码我们从status_t status = validateConnect(cameraId, /inout/clientUid)开始分析,这里cameraId=0,又,调用了另外一个接口如下:
status_t CameraService::validateConnect(int cameraId,
/*inout*/
int& clientUid) const {
int callingPid = getCallingPid();
if (clientUid == USE_CALLING_UID) {
clientUid = getCallingUid();
} else {
// We only trust our own process to forward client UIDs
if (callingPid != getpid()) {
ALOGE("CameraService::connect X (pid %d) rejected (don't trust clientUid)",
callingPid);
return PERMISSION_DENIED;
}
}
if (!mModule) {
ALOGE("Camera HAL module not loaded");
return -ENODEV;
}
if (cameraId < 0 || cameraId >= mNumberOfCameras) {
ALOGE("CameraService::connect X (pid %d) rejected (invalid cameraId %d).",
callingPid, cameraId);
return -ENODEV;
}
char value[PROPERTY_VALUE_MAX];
property_get("sys.secpolicy.camera.disabled", value, "0");
if (strcmp(value, "1") == 0) {
// Camera is disabled by DevicePolicyManager.
ALOGI("Camera is disabled. connect X (pid %d) rejected", callingPid);
return -EACCES;
}
ICameraServiceListener::Status currentStatus = getStatus(cameraId);
if (currentStatus == ICameraServiceListener::STATUS_NOT_PRESENT) {
ALOGI("Camera is not plugged in,"
" connect X (pid %d) rejected", callingPid);
return -ENODEV;
} else if (currentStatus == ICameraServiceListener::STATUS_ENUMERATING) {
ALOGI("Camera is enumerating,"
" connect X (pid %d) rejected", callingPid);
return -EBUSY;
}
// Else don't check for STATUS_NOT_AVAILABLE.
// -- It's done implicitly in canConnectUnsafe /w the mBusy array
return OK;
}
不难发现其中有针对cameraId相关判断,而mNumberOfCameras这个值恰好为0,所以导致了相机调用失败,不过mNumberOfCameras这个是有谁赋值的呢,前文中提到的CameraService::onFirstRef()在开机时第一次进行了赋值,后续拔掉外置摄像头之后mNumberOfCameras被赋值为0,如果framework层没有去调用相应接口的话,那么CameraService::getNumberOfCameras()接口不会进行后续赋值的。