Android摄像头调用失败问题

Android摄像头调用失败问题

问题描述:
之前在机顶盒上面对接视频会议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()接口不会进行后续赋值的。

你可能感兴趣的:(Andorid)