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

验证方法

在开发板上执行以下步骤验证:

  1. 运行ls /dev/video*确认系统中有哪些video节点;
  2. 执行编译好的camera_enum程序,观察输出是否列出了所有video节点;
  3. 对照v4l2-ctl --list-devices命令的输出,确认设备路径和驱动名称一致;
  4. 检查每个设备的最大分辨率是否与传感器实际能力匹配(如OV5640为2592x1944);
  5. 判定标准:程序能枚举出全部摄像头节点,且每个节点的驱动名称、分辨率信息与v4l2-ctl查询结果一致,即视为通过。
摄像头设备枚举流程 用户空间 (HAL层) CameraDeviceEnumerator enumerateDevices() getDeviceList() 设备列表 vector<CameraDeviceInfo> path, fd, position, caps 上层HAL调用 getDevicesByPosition() 打开/关闭设备 内核空间 (V4L2) open() / ioctl() VIDIOC_QUERYCAP /dev/video0 /dev/video1 摄像头驱动 ov5640 / imx219 枚举结果:设备路径 + 驱动名称 + 位置(前/后/外) + 能力集 + 最大分辨率 ▼ 系统调用 ▼