第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