赞
踩
最近项目接触的模块比较繁多而杂,因此开始写文章记录下用以总结。
目前在做的是基于openharmony3.2的多camera功能主要涉及HDF(HAL)层与framework层。
本文章涉及多路摄像头功能的第一步:支持USB摄像头插拔检测。
目前openharmony在HDF层支持的camera模式有V4L2和MPP,除了海思芯片大多用的linux通用的V4L2模式,因此在devicemanager中启动的是V4L2DeviceManager。
在V4L2DeviceManager::Init会创建一个EnumeratorManager并调用其Init。
- RetCode EnumeratorManager::Init()
- {
- uvcVideo_ = std::make_shared<HosV4L2UVC>();
- if (uvcVideo_ == nullptr) {
- CAMERA_LOGE("%s Create HosV4L2UVC fail", __FUNCTION__);
- return RC_ERROR;
- }
- uvcVideo_->V4L2UvcDetectInit([&](const std::string& hardwareName,
- std::vector<DeviceControl>& deviceControl,
- std::vector<DeviceFormat>& deviceFormat, bool uvcState) {
- UvcCallBack(hardwareName, deviceControl, deviceFormat, uvcState);
- });
- return RC_OK;
- }
可以看到上面代码初始化了一个HosV4L2UVC对象并调用其V4L2UvcDetectInit
- RetCode HosV4L2UVC::V4L2UvcDetectInit(UvcCallback cb)
- {
- // set callback
- uvcCallbackFun_ = cb;
-
- uDevFd_ = socket(PF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
- if (uDevFd_ < 0) {
- CAMERA_LOGE("UVC:V4L2Detect socket() error\n");
- return RC_ERROR;
- }
- memset_s(&nls, sizeof(nls), 0, sizeof(nls));
- nls.nl_family = AF_NETLINK;
- nls.nl_pid = getpid();
- nls.nl_groups = 1;
- rc = bind(uDevFd_, (struct sockaddr *)&nls, sizeof(nls));
-
- eventFd_ = eventfd(0, 0);
- uvcDetectThread_ = new (std::nothrow) std::thread(&HosV4L2UVC::loopUvcDevice, this);
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
聪明的朋友应该已经看出来了,这里起了个线程执行loopUvcDevice通过监听NETLINK消息来循环检测USBcamera设备的插入。
- void HosV4L2UVC::loopUvcDevice()
- {
-
- V4L2UvcEnmeDevices();
- ...
- while (g_uvcDetectEnable) {
- int rc = select(((uDevFd > eventFd) ? uDevFd : eventFd) + 1, &fds, &fds, NULL, NULL);
- if (rc > 0 && FD_ISSET(uDevFd, &fds)) {
- usleep(delayTime);
- constexpr uint32_t buffSize = 4096;
- char buf[buffSize] = {};
- unsigned int len = recv(uDevFd, buf, sizeof(buf), 0);
- if (CheckBuf(len, buf)) {
- return;
- }
- ...
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
loopUvcDevice起了个无限循环,通过select监听之前创建的fd。从fd的创建参数NETLINK_KOBJECT_UEVENT可以知道是监听的udev设备文件创建的事件。
收到后会调用CheckBuf来处理。因为会收到所有的udev事件,所以需要过滤下
- int HosV4L2UVC::CheckBuf(unsigned int len, char *buf)
- {
- constexpr uint32_t UVC_DETECT_ENABLE = 0;
- constexpr uint32_t UVC_DETECT_DISABLE = -1;
- if (len > 0 && (strstr(buf, "video4linux") != nullptr)) {
- std::lock_guard<std::mutex> lock(g_uvcDetectLock);
- if (!g_uvcDetectEnable) {
- return UVC_DETECT_DISABLE;
- }
- std::string action = "";
- std::string subsystem = "";
- std::string devnode = "";
- V4L2GetUsbString(action, subsystem, devnode, buf, len);
- UpdateV4L2UvcMatchDev(action, subsystem, devnode);
- }
- return UVC_DETECT_ENABLE;
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
每次udev信息传递都会调用CheckBuf处理,感觉这里有优化空间。
从日志看我们会收到SUBSYSTEM为video4linux,创建的设备节点为video9的消息。
由于是USB设备,也可以看下当前设备插入USB camera后的USB枚举情况
可以看到此设备匹配的uvcvideo驱动。关于底层驱动我没有去适配,应该linux系统自动支持了,后续找机会了解下。
因为之前获取到了devnode设备节点,因此就可以直接通过设备节点访问设备了。
检测到了设备后通过VIDIOC_QUERYCAP向设备节点查询设备能力,
- struct v4l2_capability {
- __u8 driver[16];
- __u8 card[32];
- __u8 bus_info[32];
- __u32 version;
- __u32 capabilities;
- __u32 device_caps;
- __u32 reserved[3];
- };
- struct v4l2_capability cap;
- rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
其中capabilities就是设备能力,查看是否支持V4L2_CAP_VIDEO_CAPTURE和V4L2_CAP_STREAMING。
camera驱动的本质就是将硬件采集到的流数据传递给用户侧。正常情况下用户侧可以使用read接口从内核读取出数据,但是这就涉及到了内核态数据和用户态数据的拷贝了。对于camera这种频繁且数据大的流效率会很低,所以目前主流都是使用内层映射的机制。
V4L2_CAP_STREAMING能力就是表示支持内存映射的方式建立buffer传递流数据。由于内核态直接将一块buffer空间映射到用户态,因此HAL层可以直接从该内存空间去除数据提高了效率。
V4L2_CAP_VIDEO_CAPTURE 则是表示这个设备是个视频捕捉设备,以此判断是否camera设备
- void HosV4L2UVC::V4L2UvcMatchDev(const std::string name, const std::string v4l2Device, bool inOut)
- {
- ....
- if ((sprintf_s(devName, sizeof(devName), "%s", name.c_str())) < 0) {
- CAMERA_LOGE("%s: sprintf devName failed", __func__);
- return;
- }
- if (inOut) {
- std::lock_guard<std::mutex> l(HosV4L2Dev::deviceFdLock_);
- iter = HosV4L2Dev::deviceMatch.insert(std::make_pair(std::string(devName), v4l2Device));
- .....
- } else {
- HosV4L2Dev::deviceMatch.erase(std::string(devName));
- }
-
- V4L2UvcSearchCapability(std::string(devName), v4l2Device, inOut);
-
- uvcCallbackFun_(std::string(devName), control_, format_, inOut);
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这个接口就是将驱动name与设备节点作为一个pair插入到HosV4L2Dev::deviceMatch表中。
之后当HAL层需要访问设备时就通过驱动name找到设备节点最终访问设备。
这里有一个问题,如果插入了多个设备并且这些设备节点都是用的同一个驱动name,那就无法区分出硬件了。因此后续还需要进行优化,使用cameraID来更细化区分,这块等实现后更新到另外的博客。
之后还需要进一步获取设备的FMT能力,也就是设备支持的分辨率、帧率等信息。
- void HosV4L2UVC::V4L2UvcSearchCapability(const std::string devName, const std::string v4l2Device, bool inOut)
- {
- ....
- std::shared_ptr<HosFileFormat> fileFormat = nullptr;
- fileFormat = std::make_shared<HosFileFormat>();
- fileFormat->V4L2GetFmtDescs(fd, format_);
- ....
- std::shared_ptr<HosV4L2Control> control = nullptr;
- control = std::make_shared<HosV4L2Control>();
- control->V4L2GetControls(fd, control_);
- }
V4L2GetFmtDescs在v4l2_fileformat.cpp中实现。下一章meta数据添加会再介绍,这里就不细讲了。主要是通过V4L2GetFmtDescs查询视频格式。
V4L2GetControls在v4l2_control.cpp中实现。通过VIDIOC_QUERYCTRL命令可以查到当前设备支持的控制命令信息。这些控制命令比如有对比度、饱和度、白平衡、曝光度等等。
最后再看看uvcCallbackFun_。
V4L2UvcDetectInit是在最开始初始化时传入的参数,倒回去看下初始化贴的代码可以知道这个回调是EnumeratorManager::UvcCallBack。然后这里又是被其他模块注册了回调。也就是最开始介绍的V4L2DeviceManager。
- void EnumeratorManager::UvcCallBack(const std::string hardwareName,
- std::vector<DeviceControl>& deviceControl,
- std::vector<DeviceFormat>& deviceFormat, bool uvcState)
- {
- uvcCb_(hardwareName, deviceControl, deviceFormat, uvcState);
- }
-
- void V4L2DeviceManager::SetHotplugDevCallBack(HotplugDevCb cb)
- {
- uvcCb_ = cb;
- enumeratorManager_->SetCallBack([&](const std::string hardwareName, std::vector<DeviceControl>& deviceControl,
- std::vector<DeviceFormat>& deviceFormat, bool uvcState) {
- UvcCallBack(hardwareName, deviceControl, deviceFormat, uvcState);
- });
- }
因此USB设备插入检测后最终会通知给V4L2DeviceManager。参数是驱动name、上面查到的控制信息、视频格式、状态(插入还是移除)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。