465 lines
13 KiB
Markdown
465 lines
13 KiB
Markdown
# Avaota F1 GC2083 摄像头驱动开发完整指南
|
||
|
||
**版本**:v1.0
|
||
**日期**:2025-11-28
|
||
**主机环境**:Ubuntu 24.04 LTS
|
||
**目标平台**:Avaota F1 (全志 V821 / 32-bit RISC-V)
|
||
**摄像头**:GC2083 (MIPI-CSI2, 1920x1080 @20fps)
|
||
|
||
---
|
||
|
||
## 第一部分:环境准备与SDK研究
|
||
|
||
### 1. 查找 GC2083 驱动
|
||
|
||
在 Tina Linux SDK 中定位相关文件:
|
||
|
||
```bash
|
||
cd ~/ProgramFiles/avaota_sdk/tina-v821-release
|
||
find . -name "*gc2083*"
|
||
```
|
||
|
||
关键文件位置:
|
||
- **驱动代码**:`lichee/linux-5.4/drivers/media/platform/sunxi-vin/modules/sensor/gc2083_mipi.c`
|
||
- **设备树配置**:`lichee/linux-5.4/arch/riscv/boot/dts/sunxi/board-v821.dtsi`
|
||
- **示例程序**:`openwrt/package/allwinner/tina_multimedia/libcedarx/demo/sample_smartIPC_demo/`
|
||
|
||
### 2. 研究 Allwinner MPP 框架
|
||
|
||
查找 MPP (Media Processing Platform) 示例:
|
||
|
||
```bash
|
||
find . -name "*sample*" | grep -E "(camera|vi|isp|venc)"
|
||
```
|
||
|
||
核心组件:
|
||
- **VI (Video Input)**:视频输入,负责从摄像头获取原始数据
|
||
- **ISP (Image Signal Processor)**:图像信号处理器
|
||
- **VENC (Video Encoder)**:视频编码器,支持 H.264/JPEG
|
||
|
||
### 3. 分析官方示例
|
||
|
||
关键示例程序:
|
||
- `sample_smartIPC_demo`:智能IPC示例(包含完整的 VI→ISP→VENC 流程)
|
||
- `sample_virvi2venc`:VI直接到编码示例
|
||
|
||
通过研究示例代码,理解了:
|
||
- MPP框架的初始化顺序:`SYS → VI → ISP → VENC`
|
||
- VI/ISP/VENC 的绑定关系
|
||
- 配置文件格式(`.conf`)
|
||
|
||
---
|
||
|
||
## 第二部分:C++ 摄像头类设计与实现
|
||
|
||
### 1. 项目结构
|
||
|
||
```
|
||
src/
|
||
├── camera/
|
||
│ ├── camera.h # 摄像头类头文件
|
||
│ └── camera.cpp # 摄像头类实现
|
||
├── test_camera.cpp # 测试程序
|
||
├── Makefile # 构建配置
|
||
└── sample_smartIPC_demo.conf # MPP配置文件
|
||
```
|
||
|
||
### 2. 关键设计决策
|
||
|
||
#### 初始化顺序
|
||
经过多次调试,确定正确的初始化顺序:
|
||
|
||
```cpp
|
||
bool Camera::init() {
|
||
// 1. 初始化 MPP 系统
|
||
AW_MPI_SYS_Init();
|
||
|
||
// 2. 创建并配置 VI 设备
|
||
AW_MPI_VI_CreateVipp(m_vi_dev);
|
||
AW_MPI_VI_SetVippAttr(m_vi_dev, &vipp_attr);
|
||
|
||
// 3. 启动 ISP
|
||
AW_MPI_ISP_Run(m_isp_dev);
|
||
|
||
// 4. 创建 VI 虚拟通道
|
||
AW_MPI_VI_CreateVirChn(m_vi_dev, m_vi_chn, &vi_chn_attr);
|
||
AW_MPI_VI_EnableVirChn(m_vi_dev, m_vi_chn);
|
||
|
||
// 5. 创建并配置 VENC
|
||
AW_MPI_VENC_CreateChn(m_venc_chn, &venc_attr);
|
||
|
||
// 6. 绑定 VI-VENC
|
||
AW_MPI_SYS_Bind(&vi_chn, &venc_chn);
|
||
|
||
return true;
|
||
}
|
||
```
|
||
|
||
#### 缓冲区配置优化
|
||
|
||
经过测试,找到最优配置:
|
||
|
||
```cpp
|
||
#define VI_BUFFER_NUM 5 // VI缓冲区数量(增加到5以提供更多缓存)
|
||
#define VBV_BUFFER_SIZE (4*1024) // VBV缓冲区4MB(之前1350KB太小)
|
||
#define DEFAULT_QUALITY 80 // JPEG质量(降低以减少VBV压力)
|
||
```
|
||
|
||
### 3. 关键实现细节
|
||
|
||
#### 帧捕获与重试机制
|
||
|
||
```cpp
|
||
bool Camera::capture_frame(uint8_t** jpeg_data, size_t* jpeg_size) {
|
||
VENC_STREAM_S stream;
|
||
VENC_PACK_S pack;
|
||
|
||
// 增加超时时间到10秒,并添加重试机制
|
||
const int max_retries = 3;
|
||
const int timeout_ms = 10000;
|
||
ERRORTYPE ret = ERR_VENC_BUF_EMPTY;
|
||
|
||
for (int retry = 0; retry < max_retries && ret != SUCCESS; retry++) {
|
||
if (retry > 0) {
|
||
LOGW("Retry %d/%d getting stream...", retry, max_retries);
|
||
usleep(100000); // 100ms延迟再重试
|
||
}
|
||
ret = AW_MPI_VENC_GetStream(m_venc_chn, &stream, timeout_ms);
|
||
}
|
||
|
||
if (ret != SUCCESS) {
|
||
LOGE("AW_MPI_VENC_GetStream failed after %d retries: 0x%x", max_retries, ret);
|
||
return false;
|
||
}
|
||
|
||
// ... 拷贝JPEG数据
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 第三部分:Makefile 配置(关键)
|
||
|
||
### 1. 工具链配置
|
||
|
||
使用 32 位 RISC-V 交叉编译器:
|
||
|
||
```makefile
|
||
CROSS_COMPILE := /path/to/riscv32-unknown-linux-
|
||
|
||
CXX := $(CROSS_COMPILE)g++
|
||
CC := $(CROSS_COMPILE)gcc
|
||
AR := $(CROSS_COMPILE)ar
|
||
STRIP := $(CROSS_COMPILE)strip
|
||
```
|
||
|
||
### 2. 头文件路径
|
||
|
||
必须包含所有 MPP 相关头文件:
|
||
|
||
```makefile
|
||
CXXFLAGS := -Wall -O2 -std=c++11
|
||
CXXFLAGS += -I$(SDK_PATH)/openwrt/staging_dir/target/usr/include
|
||
CXXFLAGS += -I$(SDK_PATH)/openwrt/staging_dir/target/usr/include/allwinner
|
||
CXXFLAGS += -I$(SDK_PATH)/openwrt/staging_dir/target/usr/include/allwinner/include
|
||
CXXFLAGS += -I$(SDK_PATH)/lichee/linux-5.4/drivers/media/platform/sunxi-vin/vin-isp/isp_server/include
|
||
CXXFLAGS += -DAWCHIP=AW_V821
|
||
```
|
||
|
||
### 3. 链接库配置(最复杂)
|
||
|
||
经过多次迭代,找到完整的库依赖:
|
||
|
||
```makefile
|
||
# MPP核心库
|
||
LDFLAGS += $(SDK_PATH)/.../libaw_mpp.a
|
||
LDFLAGS += $(SDK_PATH)/.../libmedia_utils.a
|
||
|
||
# ISP相关
|
||
LDFLAGS += $(SDK_PATH)/.../libisp.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_ini.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_ae.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_af.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_afs.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_awb.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_md.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_iso.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_gtm.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_pltm.a
|
||
LDFLAGS += $(SDK_PATH)/.../libisp_rolloff.a
|
||
|
||
# VENC相关
|
||
LDFLAGS += $(SDK_PATH)/.../libvencoder.a
|
||
LDFLAGS += $(SDK_PATH)/.../libvenc_codec.a
|
||
LDFLAGS += $(SDK_PATH)/.../libvenc_base.a
|
||
LDFLAGS += $(SDK_PATH)/.../libVE.a
|
||
LDFLAGS += $(SDK_PATH)/.../libMemAdapter.a
|
||
LDFLAGS += $(SDK_PATH)/.../libvenc_h264.a
|
||
|
||
# CedarX多媒体框架
|
||
LDFLAGS += $(SDK_PATH)/.../libcdc_base.a
|
||
LDFLAGS += $(SDK_PATH)/.../libcdx_base.a
|
||
LDFLAGS += $(SDK_PATH)/.../libcdx_common.a
|
||
LDFLAGS += $(SDK_PATH)/.../libcdx_stream.a
|
||
LDFLAGS += $(SDK_PATH)/.../libcdx_parser.a
|
||
LDFLAGS += $(SDK_PATH)/.../libcdx_muxer.a
|
||
|
||
# 音频相关(MPP依赖)
|
||
LDFLAGS += $(SDK_PATH)/.../libadecoder.a
|
||
LDFLAGS += $(SDK_PATH)/.../libcedarx_aencoder.a
|
||
LDFLAGS += $(SDK_PATH)/.../libaacenc.a
|
||
LDFLAGS += $(SDK_PATH)/.../libAgc.a
|
||
LDFLAGS += $(SDK_PATH)/.../libAec.a
|
||
LDFLAGS += $(SDK_PATH)/.../libAns.a
|
||
|
||
# 系统动态库
|
||
LDFLAGS += -lpthread -lrt -ldl -lstdc++ -lm
|
||
LDFLAGS += -lglog -lexpat -lasound -llog
|
||
```
|
||
|
||
**关键经验**:
|
||
- 库的链接顺序很重要(依赖库要放在后面)
|
||
- 必须包含所有 ISP 子模块的静态库
|
||
- 音频库虽然不直接使用,但 MPP 框架依赖它们
|
||
|
||
---
|
||
|
||
## 第四部分:调试过程与问题解决
|
||
|
||
### 问题 1:初始化失败 - ISP 无法初始化传感器
|
||
|
||
**错误日志**:
|
||
```
|
||
E0101 00:05:31.337224 isp_dev.c:476] [ISP_ERR]unable to initialize sensor subdev.
|
||
E0101 00:05:31.339653 isp_dev.c:487] [ISP_ERR]unable to open isp device[0]
|
||
```
|
||
|
||
**原因**:初始化顺序错误,先调用了 `AW_MPI_ISP_Run()` 再创建 VI 设备。
|
||
|
||
**解决**:调整顺序为 `VI创建 → ISP启动 → VENC创建`。
|
||
|
||
### 问题 2:VBV FULL - 视频缓冲区溢出
|
||
|
||
**错误日志**:
|
||
```
|
||
WARNING: jpegenc <JpegEncMainFrame:1288>: BitStreamFreeBufferSize 831936 is too small, total[1350]KB
|
||
W0101 00:08:07.503864 VideoEnc_Component.c:9615] Be careful! vencChn[0] encode [7]frames, fail BsFull
|
||
```
|
||
|
||
**原因**:
|
||
- VBV缓冲区太小(1350KB)
|
||
- VI缓冲区不够(3个)
|
||
- JPEG质量过高(90)导致数据量大
|
||
|
||
**解决方案**:
|
||
```cpp
|
||
// 优化前
|
||
#define VI_BUFFER_NUM 3
|
||
#define DEFAULT_QUALITY 90
|
||
vbv_buf_size = pic_size / 10 + vbv_thresh_size; // ~1350KB
|
||
|
||
// 优化后
|
||
#define VI_BUFFER_NUM 5 // +67%
|
||
#define VBV_BUFFER_SIZE (4*1024) // 4MB (+203%)
|
||
#define DEFAULT_QUALITY 80 // 降低质量减少数据量
|
||
```
|
||
|
||
### 问题 3:帧大小不匹配
|
||
|
||
**错误日志**:
|
||
```
|
||
W0101 00:08:08.700727 VideoEnc_Component.c:9134] fatal error! enc src_size[1280x720]!= frameSize[0x0]
|
||
```
|
||
|
||
**原因**:VBV满后,编码器无法正常处理新帧,导致内部状态异常。
|
||
|
||
**解决**:通过增大缓冲区和降低质量,避免VBV满的情况。
|
||
|
||
### 问题 4:VI超时
|
||
|
||
**错误日志**:
|
||
```
|
||
W0101 00:08:09.505139 videoInputHw.c:5672] Be careful! vipp fds select timeout[2000]ms, setNum:0!
|
||
```
|
||
|
||
**分析**:这是正常的,因为测试程序每秒只捕获一帧,VI在等待过程中超时。
|
||
|
||
**优化**:增加帧捕获间隔到1秒,给缓冲区更多恢复时间。
|
||
|
||
---
|
||
|
||
## 第五部分:测试程序与验证
|
||
|
||
### 1. 测试程序设计
|
||
|
||
```cpp
|
||
int main() {
|
||
Camera camera;
|
||
|
||
// 1. 初始化摄像头
|
||
if (!camera.init()) {
|
||
printf("ERROR: Camera initialization failed!\n");
|
||
return -1;
|
||
}
|
||
|
||
// 2. 设置JPEG质量
|
||
camera.set_quality(80);
|
||
|
||
// 3. 等待ISP稳定
|
||
sleep(2);
|
||
|
||
// 4. 连续捕获10张照片
|
||
for (int i = 0; i < 10; i++) {
|
||
uint8_t* jpeg_data = nullptr;
|
||
size_t jpeg_size = 0;
|
||
|
||
if (!camera.capture_frame(&jpeg_data, &jpeg_size)) {
|
||
printf("ERROR: Failed to capture frame %d\n", i+1);
|
||
continue;
|
||
}
|
||
|
||
// 保存到SD卡
|
||
char filename[256];
|
||
snprintf(filename, sizeof(filename), "/mnt/extsd/pic_%03d.jpg", i);
|
||
FILE* fp = fopen(filename, "wb");
|
||
fwrite(jpeg_data, 1, jpeg_size, fp);
|
||
fclose(fp);
|
||
|
||
camera.release_frame(jpeg_data);
|
||
|
||
// 1秒间隔
|
||
usleep(1000000);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### 2. 编译与部署
|
||
|
||
```bash
|
||
# 编译
|
||
cd ~/ProgramFiles/AvaotaF1/avaota_app_demo/src
|
||
make clean
|
||
make test_camera
|
||
|
||
# 复制到SD卡
|
||
cp test_camera /media/user/SD_CARD/
|
||
|
||
# 在板子上运行
|
||
mount /dev/mmcblk0p1 /mnt/extsd
|
||
cp /mnt/extsd/test_camera /tmp/
|
||
chmod +x /tmp/test_camera
|
||
/tmp/test_camera
|
||
```
|
||
|
||
### 3. 测试结果
|
||
|
||
**优化前**(失败案例):
|
||
```
|
||
Total captured: 6/10
|
||
Success rate: 60.0%
|
||
Final FPS: 2.0
|
||
```
|
||
|
||
**优化后**(成功!):
|
||
```
|
||
Total captured: 10/10
|
||
Success rate: 100.0%
|
||
Final FPS: 1.0
|
||
Pictures saved to: /mnt/extsd/
|
||
```
|
||
|
||
**优化效果对比**:
|
||
|
||
| 指标 | 优化前 | 优化后 | 改进 |
|
||
|------|--------|--------|------|
|
||
| 成功率 | 60% | **100%** | +40% |
|
||
| VI缓冲区 | 3 | **5** | +67% |
|
||
| VBV缓冲区 | 1350KB | **4096KB** | +203% |
|
||
| JPEG质量 | 90 | **80** | 优化 |
|
||
| 文件大小 | 58-145KB | **34-88KB** | -40% |
|
||
| 严重错误 | 大量 | **无** | ✓ |
|
||
|
||
---
|
||
|
||
## 第六部分:经验总结与最佳实践
|
||
|
||
### 1. MPP框架使用要点
|
||
|
||
✅ **必须遵守的初始化顺序**:
|
||
```
|
||
SYS_Init → VI_Create → VI_SetAttr → ISP_Run → VI_CreateVirChn → VENC_Create → SYS_Bind
|
||
```
|
||
|
||
✅ **缓冲区配置原则**:
|
||
- VI缓冲区:至少5个,提供足够的帧缓存
|
||
- VBV缓冲区:根据分辨率和质量设置,建议4MB起步
|
||
- 质量设置:JPEG质量80-85为最佳平衡点
|
||
|
||
✅ **错误处理**:
|
||
- 所有MPP API调用都要检查返回值
|
||
- 重要的初始化步骤要添加详细日志
|
||
- 捕获帧时添加重试机制(3次,100ms间隔)
|
||
|
||
### 2. 编译链接要点
|
||
|
||
❌ **常见错误**:
|
||
- 缺少某个 ISP 子模块静态库
|
||
- 库的链接顺序不对
|
||
- 忘记定义 `AWCHIP` 宏
|
||
|
||
✅ **解决方案**:
|
||
- 参考 SDK 中 `tina.mk` 的配置
|
||
- 使用 `nm` 工具检查未定义符号
|
||
- 按依赖关系排列库的顺序
|
||
|
||
### 3. 调试技巧
|
||
|
||
📊 **日志分析**:
|
||
- 关注 `[ISP_ERR]` 和 `fatal error`
|
||
- VBV FULL 警告是性能瓶颈的信号
|
||
- VI timeout 可能是正常现象(低帧率捕获时)
|
||
|
||
🔍 **性能分析**:
|
||
- 通过日志中的 `vfmt.bufs` 查看实际VI缓冲区数
|
||
- 通过 `BitStreamFreeBufferSize` 监控VBV使用情况
|
||
- FPS统计帮助判断系统负载
|
||
|
||
---
|
||
|
||
## 🏆 最终验证通过的开发流程
|
||
|
||
现在你已经拥有了一套**经过实战验证的GC2083摄像头驱动开发流程**:
|
||
|
||
1. **SDK研究**:定位驱动和示例代码,理解MPP框架
|
||
2. **类设计**:封装VI/ISP/VENC,提供简洁的API
|
||
3. **Makefile配置**:正确配置头文件路径和链接库
|
||
4. **初始化调试**:确保正确的初始化顺序
|
||
5. **缓冲区优化**:根据实际情况调整缓冲区大小
|
||
6. **测试验证**:通过测试程序验证功能
|
||
7. **性能优化**:分析日志,调整参数达到最佳性能
|
||
|
||
### 关键配置文件清单
|
||
|
||
- ✅ `camera/camera.h` - 摄像头类头文件
|
||
- ✅ `camera/camera.cpp` - 摄像头类实现
|
||
- ✅ `test_camera.cpp` - 测试程序
|
||
- ✅ `Makefile` - 完整的编译配置
|
||
- ✅ `sample_smartIPC_demo.conf` - MPP配置文件
|
||
|
||
### 最终测试结果
|
||
|
||
```
|
||
==========================================
|
||
Test Complete!
|
||
==========================================
|
||
Total captured: 10/10
|
||
Success rate: 100.0%
|
||
Final FPS: 1.0
|
||
Pictures saved to: /mnt/extsd/
|
||
==========================================
|
||
```
|
||
|
||
🎉 **GC2083 摄像头驱动开发成功!** 🎉
|