在使用外接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 是一个强大的工具,但也有其限制,例如它无法监控递归目录,因此需要根据具体需求进行适配。
记得处理所有可能的错误情况,并确保资源正确释放。