第28章:Camera开源项目分析

做Camera驱动开发这些年,我接触过不少开源项目。说实话,每个项目都有自己的脾气。AOSP的HAL层中规中矩,Rockchip的驱动透着浓浓的嵌入式风格,Qualcomm的CamX/CHI则像一头巨兽——功能强大但学习曲线陡峭。今天咱们就把这些项目挨个捋一遍。

28.1 AOSP Camera HAL源码分析

AOSP的Camera HAL,说白了就是Google给厂商画的一个框。它定义了CameraProvider、CameraDevice、CameraStream这些接口,厂商得按这个框架往里填代码。

我刚开始看AOSP HAL时,最头疼的是它的分层结构。你看:

// CameraProvider.h - 核心接口
class ICameraProvider : public IInterface {
public:
    virtual status_t getCameraIdList(
        std::vector<std::string>* cameraIds) = 0;
    virtual status_t getCameraDeviceInterface(
        const std::string& cameraId,
        sp<ICameraDevice>* device) = 0;
};

这个ICameraProvider是所有Camera设备的入口。每个Camera设备对应一个ICameraDevice,而ICameraDevice又管理着多个Stream。嗯,这里要注意:AOSP HAL 3.x之后,Stream的配置变得非常灵活,但也容易踩坑。

核心要点:AOSP Camera HAL的设计哲学是「接口隔离」。Provider只管发现设备,Device只管操作设备,Stream只管数据流。各司其职,互不干扰。

我在项目中遇到过一个问题:某款Sensor在初始化时总是返回-ENODEV。查了半天,发现是Provider层没有正确枚举设备。说白了,就是getCameraIdList里漏掉了这个Sensor的ID。

28.2 Rockchip Camera驱动源码分析(rkisp1)

Rockchip的rkisp1驱动,是我见过最「接地气」的Camera驱动之一。它直接操作ISP硬件寄存器,没有那么多抽象层。

rkisp1的核心数据结构是rkisp1_device:

struct rkisp1_device {
    struct device *dev;
    void __iomem *base_addr;
    struct clk *clks[RKISP1_MAX_CLKS];
    struct rkisp1_isp_subdev isp_sd;
    struct rkisp1_csi_subdev csi_sd;
    struct rkisp1_params_subdev params_sd;
    struct rkisp1_stats_subdev stats_sd;
    struct media_device media_dev;
};

你想想看,这个结构体把ISP、CSI、参数、统计信息全塞在一起了。Rockchip的做法是:一个设备节点,搞定所有事情。这和Qualcomm那种「每个模块一个驱动」的思路完全不同。

个人经验:调试rkisp1时,建议先看dts配置。Rockchip的驱动对dts依赖非常大,很多参数(如MIPI时钟、Sensor型号)都在dts里配。我曾经因为dts里配错了CSI的lane数,折腾了两天才发现。

rkisp1的ISP pipeline很有意思:Sensor数据→CSI接收→ISP处理→DMA输出。每个阶段都有对应的subdev。调试时可以用media-ctl命令查看pipeline状态:

# media-ctl -p -d /dev/media0
- entity 1: rkisp1-isp (1 pad, 1 link)
            type V4L2 subdev subtype Sensor
            pad0: Source
                    [fmt:SBGGR10/1920x1080]
                    -> "rkisp1_resizer_mainpath":0 []

28.3 Qualcomm Camera驱动架构(CamX/CHI)

Qualcomm的CamX/CHI架构,嗯,怎么说呢,它把Camera驱动做成了一个「插件系统」。CHI(Camera Hardware Interface)是核心,CamX是上层封装。每个Sensor、每个ISP模块都是一个独立的CHI节点。

我刚开始接触CamX时,被它的XML配置文件吓到了。一个Sensor的配置,可能要涉及十几个XML文件:

<!-- sensor_config.xml -->
<SensorConfig>
    <SensorId>OV5640</SensorId>
    <ModuleName>ov5640_module</ModuleName>
    <StreamConfig>
        <Stream size="1920x1080" fps="30" />
        <Stream size="640x480" fps="60" />
    </StreamConfig>
    <TuningFile>ov5640_tuning.bin</TuningFile>
</SensorConfig>

避坑指南:我曾经在移植新Sensor到CamX时,漏掉了Usecase XML的配置。结果Camera HAL能识别Sensor,但上层应用死活打不开Camera。查了三天,才发现是Usecase里没有定义对应的Stream组合。Qualcomm的XML配置,少一个都不行。

CamX/CHI的架构图大致是这样的:

Application
    ↓
CamX API (Camera HAL3)
    ↓
CHI Core (Pipeline Manager)
    ↓
CHI Nodes (Sensor, ISP, Stats...)
    ↓
Kernel Drivers (V4L2, ION...)

每个CHI Node都是一个独立的动态库。你添加新Sensor时,只需要写一个对应的CHI Node库,然后注册到CHI Core里。这种设计的好处是模块化,坏处是——调试起来非常痛苦,因为错误可能出现在任何一个Node里。

28.4 开源Camera项目(libcamera/OpenCamera)

libcamera是近年来最值得关注的开源Camera框架。它试图统一Linux上的Camera开发体验。说白了,就是不想让每个厂商都搞一套自己的HAL。

libcamera的核心概念是PipelineHandler:

class PipelineHandler : public Object {
public:
    virtual int configure(Camera *camera, StreamConfig &config) = 0;
    virtual int start(Camera *camera) = 0;
    virtual int stop(Camera *camera) = 0;
    virtual int queueRequest(Request *request) = 0;
};

每个Camera设备对应一个PipelineHandler。libcamera已经内置了UVC、Raspberry Pi、Rockchip等平台的PipelineHandler。你添加新Sensor时,要么复用已有的PipelineHandler,要么自己写一个。

OpenCamera则是一个Android端的开源Camera应用。它直接调用Android Camera2 API,不涉及底层驱动。但它的代码结构非常清晰,适合学习Camera应用层的开发流程。

28.5 实战:为libcamera添加新Sensor支持

好,咱们来点实际的。假设你手头有一个OV5640 Sensor,要把它加到libcamera里。怎么做?

第一步:创建Sensor配置

在libcamera的src/ipa/目录下,创建一个ov5640.yaml文件:

# ov5640.yaml
sensor:
  name: "OV5640"
  i2c_address: 0x3c
  mclk: 24000000
  formats:
    - format: SGBRG10
      size: 2592x1944
      fps: 15
    - format: SGBRG10
      size: 1920x1080
      fps: 30

第二步:实现CameraSensor类

继承CameraSensor基类,实现初始化、配置、流控制等方法:

class OV5640CameraSensor : public CameraSensor {
public:
    int init() override {
        // 读取OV5640的芯片ID
        uint16_t chip_id = readRegister(0x300A);
        if (chip_id != 0x5640) {
            LOG(Error) << "Not an OV5640 sensor";
            return -ENODEV;
        }
        return 0;
    }
    
    int configure(const StreamConfig &config) override {
        // 根据分辨率配置Sensor寄存器
        if (config.size == Size(1920, 1080)) {
            writeRegister(0x3808, 0x07); // width high
            writeRegister(0x3809, 0x80); // width low
            // ... 更多配置
        }
        return 0;
    }
};

第三步:注册到PipelineHandler

在对应的PipelineHandler里,添加OV5640的匹配规则:

class SimplePipelineHandler : public PipelineHandler {
public:
    bool match(DeviceEnumerator *enumerator) override {
        // 匹配OV5640的I2C地址
        DeviceMatch dm("ov5640");
        dm.addId("i2c:0x3c");
        
        if (enumerator->search(dm)) {
            registerCamera(new OV5640CameraSensor());
            return true;
        }
        return false;
    }
};

我的建议:添加新Sensor时,先用v4l2-ctl测试一下Sensor能否正常工作。libcamera的调试信息可以通过LIBCAMERA_LOG_LEVELS环境变量控制。我习惯设置成DEBUG级别,这样能看到每个Pipeline的调用过程。

说实话,为libcamera添加新Sensor支持,工作量并不大。真正麻烦的是调优——比如AE、AWB的参数配置,这些需要反复试验。我曾在某个项目上,为了调好OV5640的自动曝光,整整花了一周时间。

好了,这一章的内容就到这里。开源Camera项目各有千秋,选哪个取决于你的平台和需求。AOSP HAL适合Android生态,Rockchip驱动适合嵌入式Linux,CamX/CHI适合高端手机,libcamera则是最有潜力的统一方案。你自己根据项目情况选吧。


公众号:蓝海资料掘金营,微信deep3321