2. 枚举摄像头设备
任务说明
在相机HAL驱动开发中,第一步是正确枚举系统中可用的摄像头设备。本案例要求实现一个设备枚举模块,能够扫描Linux系统下的/dev/video*节点,识别每个节点对应的摄像头类型(前置/后置)、能力集(是否支持预览/拍照/录像),并输出一份结构化的设备列表供上层HAL调用。
核心实现
// camera_device_enum.h
#ifndef CAMERA_DEVICE_ENUM_H
#define CAMERA_DEVICE_ENUM_H
#include <linux/videodev2.h>
#include <string>
#include <vector>
#include <map>
enum class CameraPosition {
FRONT,
BACK,
EXTERNAL,
UNKNOWN
};
enum class CameraCapability {
PREVIEW = 1 << 0,
STILL_CAPTURE = 1 << 1,
VIDEO_RECORD = 1 << 2
};
struct CameraDeviceInfo {
std::string device_path; // /dev/videoN
int fd; // 打开的文件描述符
CameraPosition position; // 物理位置
uint32_t capabilities; // 位掩码,CameraCapability组合
uint32_t width; // 最大分辨率宽
uint32_t height; // 最大分辨率高
std::string driver_name; // 驱动名称
};
class CameraDeviceEnumerator {
public:
CameraDeviceEnumerator();
~CameraDeviceEnumerator();
// 扫描/dev/video*节点,填充设备列表
bool enumerateDevices();
// 获取枚举结果
const std::vector<CameraDeviceInfo>& getDeviceList() const;
// 按位置过滤设备
std::vector<CameraDeviceInfo> getDevicesByPosition(CameraPosition pos) const;
// 释放所有打开的设备句柄
void closeAllDevices();
private:
std::vector<CameraDeviceInfo> devices_;
std::map<std::string, int> fd_map_; // path -> fd映射
bool queryDeviceCapabilities(CameraDeviceInfo& dev);
CameraPosition detectPosition(const std::string& driver_name);
bool getMaxResolution(int fd, uint32_t& w, uint32_t& h);
};
#endif
// camera_device_enum.cpp
#include "camera_device_enum.h"
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <cstring>
#include <algorithm>
#include <iostream>
CameraDeviceEnumerator::CameraDeviceEnumerator() {}
CameraDeviceEnumerator::~CameraDeviceEnumerator() {
closeAllDevices();
}
bool CameraDeviceEnumerator::enumerateDevices() {
DIR* dir = opendir("/dev");
if (!dir) {
std::cerr << "Failed to open /dev directory" << std::endl;
return false;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (strncmp(entry->d_name, "video", 5) != 0) {
continue;
}
// 只处理video后跟数字的节点,如video0, video1
bool is_number = true;
for (const char* p = entry->d_name + 5; *p; ++p) {
if (!isdigit(*p)) {
is_number = false;
break;
}
}
if (!is_number) continue;
std::string path = std::string("/dev/") + entry->d_name;
int fd = open(path.c_str(), O_RDWR);
if (fd < 0) {
std::cerr << "Failed to open " << path << std::endl;
continue;
}
CameraDeviceInfo info;
info.device_path = path;
info.fd = fd;
// 查询驱动名称
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
info.driver_name = reinterpret_cast<const char*>(cap.driver);
info.position = detectPosition(info.driver_name);
} else {
info.driver_name = "unknown";
info.position = CameraPosition::UNKNOWN;
}
// 查询能力与分辨率
if (!queryDeviceCapabilities(info)) {
std::cerr << "Failed to query capabilities for " << path << std::endl;
close(fd);
continue;
}
devices_.push_back(info);
fd_map_[path] = fd;
}
closedir(dir);
return !devices_.empty();
}
bool CameraDeviceEnumerator::queryDeviceCapabilities(CameraDeviceInfo& dev) {
// 检查是否支持视频捕获
struct v4l2_capability cap;
if (ioctl(dev.fd, VIDIOC_QUERYCAP, &cap) < 0) {
return false;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
return false; // 不是视频捕获设备
}
dev.capabilities = 0;
if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
dev.capabilities |= static_cast<uint32_t>(CameraCapability::PREVIEW);
}
// 检查是否支持视频输出(用于录像)
if (cap.capabilities & V4L2_CAP_VIDEO_OUTPUT) {
dev.capabilities |= static_cast<uint32_t>(CameraCapability::VIDEO_RECORD);
}
// 检查是否支持静态图像捕获
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(dev.fd, VIDIOC_G_FMT, &fmt) == 0) {
// 如果支持设置格式,通常也支持拍照
dev.capabilities |= static_cast<uint32_t>(CameraCapability::STILL_CAPTURE);
}
// 获取最大分辨率
if (!getMaxResolution(dev.fd, dev.width, dev.height)) {
dev.width = 0;
dev.height = 0;
}
return true;
}
CameraPosition CameraDeviceEnumerator::detectPosition(const std::string& driver_name) {
// 根据驱动名称判断摄像头位置
// 实际项目中通常通过设备树或ACPI表获取,这里做简化处理
std::string lower = driver_name;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
if (lower.find("front") != std::string::npos ||
lower.find("ov5640_front") != std::string::npos) {
return CameraPosition::FRONT;
}
if (lower.find("back") != std::string::npos ||
lower.find("imx219") != std::string::npos ||
lower.find("ov5647") != std::string::npos) {
return CameraPosition::BACK;
}
if (lower.find("uvc") != std::string::npos) {
return CameraPosition::EXTERNAL;
}
return CameraPosition::UNKNOWN;
}
bool CameraDeviceEnumerator::getMaxResolution(int fd, uint32_t& w, uint32_t& h) {
struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
uint32_t max_w = 0, max_h = 0;
for (int i = 0; ; ++i) {
fmtdesc.index = i;
if (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0) {
break;
}
struct v4l2_frmsizeenum frmsize;
memset(&frmsize, 0, sizeof(frmsize));
frmsize.index = 0;
frmsize.pixel_format = fmtdesc.pixelformat;
while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) {
if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
if (frmsize.discrete.width > max_w) {
max_w = frmsize.discrete.width;
max_h = frmsize.discrete.height;
}
} else if (frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS ||
frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
max_w = frmsize.stepwise.max_width;
max_h = frmsize.stepwise.max_height;
break;
}
frmsize.index++;
}
}
if (max_w > 0 && max_h > 0) {
w = max_w;
h = max_h;
return true;
}
return false;
}
const std::vector<CameraDeviceInfo>& CameraDeviceEnumerator::getDeviceList() const {
return devices_;
}
std::vector<CameraDeviceInfo> CameraDeviceEnumerator::getDevicesByPosition(CameraPosition pos) const {
std::vector<CameraDeviceInfo> result;
for (const auto& dev : devices_) {
if (dev.position == pos) {
result.push_back(dev);
}
}
return result;
}
void CameraDeviceEnumerator::closeAllDevices() {
for (auto& pair : fd_map_) {
close(pair.second);
}
fd_map_.clear();
devices_.clear();
}
// main.cpp 示例调用
#include "camera_device_enum.h"
#include <iostream>
int main() {
CameraDeviceEnumerator enumerator;
if (!enumerator.enumerateDevices()) {
std::cerr << "No camera devices found" << std::endl;
return -1;
}
const auto& devices = enumerator.getDeviceList();
std::cout << "Found " << devices.size() << " camera device(s):" << std::endl;
for (const auto& dev : devices) {
std::cout << " Path: " << dev.device_path << std::endl;
std::cout << " Driver: " << dev.driver_name << std::endl;
std::cout << " Position: ";
switch (dev.position) {
case CameraPosition::FRONT: std::cout << "Front"; break;
case CameraPosition::BACK: std::cout << "Back"; break;
case CameraPosition::EXTERNAL: std::cout << "External"; break;
default: std::cout << "Unknown"; break;
}
std::cout << std::endl;
std::cout << " Max Resolution: " << dev.width << "x" << dev.height << std::endl;
std::cout << " Capabilities: ";
if (dev.capabilities & static_cast<uint32_t>(CameraCapability::PREVIEW))
std::cout << "[Preview] ";
if (dev.capabilities & static_cast<uint32_t>(CameraCapability::STILL_CAPTURE))
std::cout << "[Still] ";
if (dev.capabilities & static_cast<uint32_t>(CameraCapability::VIDEO_RECORD))
std::cout << "[Record] ";
std::cout << std::endl << std::endl;
}
enumerator.closeAllDevices();
return 0;
}
编译与运行
# 编译(需要Linux内核头文件)
g++ -std=c++11 -o camera_enum camera_device_enum.cpp main.cpp -I/usr/include
# 运行(需要root权限或video组权限)
sudo ./camera_enum
验证方法
在开发板上执行以下步骤验证:
- 运行
ls /dev/video*确认系统中有哪些video节点; - 执行编译好的
camera_enum程序,观察输出是否列出了所有video节点; - 对照
v4l2-ctl --list-devices命令的输出,确认设备路径和驱动名称一致; - 检查每个设备的最大分辨率是否与传感器实际能力匹配(如OV5640为2592x1944);
- 判定标准:程序能枚举出全部摄像头节点,且每个节点的驱动名称、分辨率信息与
v4l2-ctl查询结果一致,即视为通过。