使用外接usb摄像头预览时拔出摄像头再插上相机ID会后移

时间:2024-10-05 12:36:28

在使用外接USB摄像头时,在预览过程中,将外接摄像头移除,再插入该摄像头的ID会后移一位,这种情况发生是因为摄像头再拔出时没有触发驱动的release方法释放资源,导致再次插入摄像头时之前的ID被占用而后移。

一、分析逻辑,为什么摄像头预览拔出会出问题

common/drivers/media/usb/uvc/uvc_driver.c 这里是UVC驱动代码,插上USB时会触发usb_probe,拔出时会触发uvc_disconnet。

Uvc_probe 最终会调用到 的函数__video_register_device中,每次新摄像头的节点是通过devnode_find来查找,通过函数devnode_set 把驱动节点标记为正在使用中,通过devnode_clear可以清除节点的占用状态。

但是我们发现异常拔出的Uvc_disconnect一路调用中并没有devnode_clear这个函数的调用,所以异常拔出的时候不会清除节点使用状态。这就是问题所在

二、正常退出预览为什么不会有问题?

/* Register the release callback that will be called when the last
       reference to the device goes away. */
    vdev->dev.release = v4l2_device_release;
  • 1
  • 2
  • 3

每个节点会注册一个release方法,而v4l2_device_release函数中会调用devnode_clear所以正常退出预览不会有问题。

三、问题找到了如何解决

1.在驱动解决

在 中video_unregister_device中加入devnode_clear,即可在摄像头拔出时清除掉暂用状态,但是这种办法仅仅适用于AOSP版本,如果是要过GMS认证的版本因为GKI的限制这部分标准驱动是不能被修改的,那么对于需要过GMS认证的版本需要采用第二种方法

2.在HAL层解决

HAL层分两种情况:
第一种相机使用legacy provider:
修改 hardware/amlogic/camera/

void AppCallbackNotifier::errorNotify(int error)
{
   LOG_FUNCTION_NAME;

   CAMHAL_LOGEB("AppCallbackNotifier received error %d", error);

   // If it is a fatal error abort here!
   if((error == CAMERA_ERROR_FATAL) || (error == CAMERA_ERROR_HARD)) {
       //We kill media server if we encounter these errors as there is
       //no point continuing and apps also don't handle errors other
       //than media server death always.
       abort();
       return;
   }    
   if (  ( NULL != mCameraHal ) && 
         ( NULL != mNotifyCb ) && 
         ( mCameraHal->msgTypeEnabled(CAMERA_MSG_ERROR) ) )
     {    
       CAMHAL_LOGEB("AppCallbackNotifier mNotifyCb %d", error);
   	mCameraHal->release();                        // add 添加这行
       mNotifyCb(CAMERA_MSG_ERROR, CAMERA_ERROR_UNKNOWN, 0, mCallbackCookie);
     }    

   LOG_FUNCTION_NAME_EXIT;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

第二种,使用的external provider,这种Google原生相机服务没有做相关处理逻辑,自己添加节点检测,在检测到节点移除之后去对打开的节点执行close操作。
interfaces/camera/device/3.4/default/include/ext_device_v3_4_impl/
添加

// xbh patch begin
    class MonitorThread : public android::Thread {
    private:
        const wp<ExternalCameraDeviceSession> mParent;
        int mFd;
        int mCameraId;
    public:
        MonitorThread(wp<ExternalCameraDeviceSession> parent, int fd, int cameraId) :
                 mParent(parent), mFd(fd), mCameraId(cameraId){};
        ~MonitorThread(){};
        virtual bool threadLoop() override;
    };  
    // xbh patch end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

interfaces/camera/device/3.4/default/
添加


#include <iostream>
#include <thread>
#include <>
#include <>
#include <sys/>
#include <cstring>
#include <functional>

#define EVENT_SIZE (sizeof(struct inotify_event))
#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))
 
......
 
 bool ExternalCameraDeviceSession::initialize() {
    if (mV4l2Fd.get() < 0) {
        ALOGE("%s: invalid v4l2 device fd %d!", __FUNCTION__, mV4l2Fd.get());
        return true;
    }
	// add 添加如下线程监控
	int cameraId = atoi(mCameraId.c_str());
    int fd = mV4l2Fd.get();
    sp<MonitorThread> monitorThread = new MonitorThread(this, fd, cameraId);
    monitorThread->run("ExtCamMonitor", PRIORITY_DISPLAY);
    // add end
	......
}
 
bool ExternalCameraDeviceSession::MonitorThread::threadLoop()
{
    ALOGD("MonitorDevice thread : fd = %d, cameraId = %d", mFd, mCameraId);
    auto parent = mParent.promote();
    int inotifyFd = inotify_init();
    if (inotifyFd < 0) {
        ALOGE("inotify_init error");
        return false;
    }
 
    int watchFd = inotify_add_watch(inotifyFd, "/dev", IN_DELETE);
    if (watchFd < 0) {
        ALOGE("inotify_add_watch error");
        ::close(inotifyFd);
        return false;
    }
    char buffer[EVENT_BUF_LEN];
    char devName[8];
    sprintf(devName,"%s%d","video", (mCameraId % 100));
    ALOGD("monitor device : %s", devName);
    while (true) {
        int length = read(inotifyFd, buffer, EVENT_BUF_LEN);
        if (length < 0) {
            ALOGE("inotifyFd read error");
        }
int i = 0;
        while (i < length) {
            struct inotify_event* event = (struct inotify_event*)&buffer[i];
            if (event->len) {
                if (event->mask & IN_DELETE) {
                    ALOGV("Device removed: %s", event->name);
                    if (!strcmp(event->name, devName)) {
                        ALOGD("Camera %s:%d removed.", devName, mFd);
if (parent) {
                            parent->close(true);
                        }
                        if (mFd != -1) {
                            ::close(mFd);
                            mFd = -1;
                        }
                        ALOGD("File descriptor closed");
                        goto exit;
                    }
                }
            }
            i += EVENT_SIZE + event->len;
        }
    }
exit:
    inotify_rm_watch(inotifyFd, watchFd);
    ::close(inotifyFd);
 
    ALOGD("monitorDevice thread stop");
 
    return false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

在 C++ 中,你可以使用 inotify 来监控设备节点(例如 /dev/video0)的变化,从而在节点消失时对已打开的文件描述符进行 close 操作。

使用 inotify 监控设备节点
inotify 是 Linux 内核中的一个机制,用于监控文件系统事件,例如文件创建、删除、修改等。你可以使用它来监控 /dev 目录中设备节点的变化。

代码说明

1、初始化 inotify:

int inotifyFd = inotify_init();
if (inotifyFd < 0) {
    perror("inotify_init");
    return;
}
  • 1
  • 2
  • 3
  • 4
  • 5

2、添加监控 /dev 目录中的删除事件:

int watchFd = inotify_add_watch(inotifyFd, "/dev", IN_DELETE);
if (watchFd < 0) {
    perror("inotify_add_watch");
    close(inotifyFd);
    return;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、读取和处理 inotify 事件:

	char buffer[EVENT_BUF_LEN];
    char devName[8];
    sprintf(devName,"%s%d","video", (mCameraId % 100));
    ALOGD("monitor device : %s", devName);
	while (true) {
        int length = read(inotifyFd, buffer, EVENT_BUF_LEN);
        if (length < 0) {
            ALOGE("inotifyFd read error");
        }
int i = 0;
        while (i < length) {
            struct inotify_event* event = (struct inotify_event*)&buffer[i];
            if (event->len) {
                if (event->mask & IN_DELETE) {
                    ALOGV("Device removed: %s", event->name);
                    if (!strcmp(event->name, devName)) {
                        ALOGD("Camera %s:%d removed.", devName, mFd);
if (parent) {
                            parent->close(true);
                        }
                        if (mFd != -1) {
                            ::close(mFd);
                            mFd = -1;
                        }
                        ALOGD("File descriptor closed");
                        goto exit;
                    }
                }
            }
            i += EVENT_SIZE + event->len;
        }
    }
    exit:
    inotify_rm_watch(inotifyFd, watchFd);
    ::close(inotifyFd);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

4、创建和启动监控线程:

	int cameraId = atoi(mCameraId.c_str());
    int fd = mV4l2Fd.get();
    sp<MonitorThread> monitorThread = new MonitorThread(this, fd, cameraId);
    monitorThread->run("ExtCamMonitor", PRIORITY_DISPLAY);
  • 1
  • 2
  • 3
  • 4

在这里有个坑,如果使用c++的标准线程这里会导致相机服务崩溃,需要使用Android内置的线程
注意事项
在实际应用中,可能需要更复杂的逻辑来处理线程的生命周期和退出条件。
inotify 是一个强大的工具,但也有其限制,例如它无法监控递归目录,因此需要根据具体需求进行适配。
记得处理所有可能的错误情况,并确保资源正确释放。