1. 整体代码结构
2. 摄像头采集模块
camera_100ask_dev.c/.h —— 摄像头采集模块
负责摄像头设备的初始化、图像采集、格式转换、缩放、亮度调节、保存图像。
1. 初始化函数
int camera_100ask_dev_init(char * dev)
完成如下操作:
- 初始化显示模块和获取 LCD 分辨率;
- 初始化摄像头设备并获取图像格式;
- 初始化图像格式转换模块;
- 启动摄像头;
- 创建采集线程
thread_camera_work。
2. 图像采集线程
static void *thread_camera_work(void *args)
该线程持续循环执行图像采集和处理流程:
-
采集一帧图像;
-
格式转换(如 YUYV → RGB);
-
如果图像尺寸大于屏幕则缩放;
-
图像可合并到 framebuffer;
-
执行命令选项,如:
- 调整亮度(
CAMERA_100ASK_OPT_UPDATE_BRIGHTNESS) - 拍照保存 BMP 图像(
CAMERA_100ASK_OPT_TAKE_PHOTOS)
- 调整亮度(
可调参数与接口
camera_100ask_dev_set_brightness(int)设置亮度camera_100ask_dev_set_opt(enum)设置当前操作(拍照、调亮度等)camera_100ask_dev_get_video_buf_cur()获取当前图像缓冲区指针
为啥需要camera_100ask_dev.c/.h?
采集、转换、缩放、合并显示的代码已经实现,为啥还要camera_100ask_dev.c/.h?
从功能角度来看,camera_100ask_dev.c/.h 并不是必需的。但它的价值主要体现在 结构封装、接口抽象和线程管理 三个层面:
1. 模块化封装(让代码更清晰)
camera_100ask_dev 提供了一个干净的接口层,把以下逻辑封装起来:
- 设备初始化(摄像头 + 显示 + 转换器)
- 图像处理线程(采集 + 转换 + 缩放 + 保存)
- 操作选项(拍照、调亮度)
➡️ 它把“工作逻辑”和“UI逻辑”分开,减少耦合。UI层不需要直接操作 V4L2,只要调用: camera_100ask_dev_set_opt(CAMERA_100ASK_OPT_TAKE_PHOTOS);
2. 统一线程管理(采集线程集中管理)
这个文件自己启动了一个线程 thread_camera_work,并负责:
- 持续采集图像
- 根据 UI 指令触发亮度调节、拍照、保存等操作
- 控制帧缓冲输出(注释中的 framebuffer 合成可以打开)
➡️ 避免 UI 线程做耗时操作,保持系统响应流畅。
3. 状态管理 & 全局控制变量
提供全局状态变量(例如亮度、当前操作),并保证通过 pthread_mutex 进行同步,避免并发冲突。
3. 用户界面模块(LVGL)
camera_100ask_ui.c —— 用户界面模块(LVGL)
负责显示界面、用户操作处理,包括拍照按钮、亮度滑动条、图片浏览器
1. UI组件
- 启动动画
lv_100ask_boot_animation - 中央摄像头图像区域
- 拍照按钮(调用
camera_100ask_dev_set_opt(TAKE_PHOTOS)) - 设置按钮 + 亮度滑块
- 本地 BMP 图像浏览器(左右切换)
2. 核心事件处理
btn_capture_event_handler:拍照命令下发btn_setting_event_handler:亮度滑块显示/隐藏slider_setting_event_cb:亮度值变化触发设置camera_work_timer:定时刷新摄像头图像到 UIbtn_open_photo_browser_event_handler:打开图片浏览界面btn_photo_browser_event_handler:切换上一张/下一张图片
4. 面试问题总结
🧠 一、V4L2 图像采集与处理(底层)
- V4L2 数据采集流程中,常见的四个 ioctl 调用分别是什么?作用是什么?
- V4L2 中常见的图像格式有哪些?你如何处理 YUYV 格式图像?
- 如何通过 V4L2 设置摄像头的亮度?用到哪些 ioctl 命令?
- 你是如何判断当前摄像头支持的像素格式和分辨率的?
- 在图像采集线程中,如何判断是否采集成功?失败了如何处理?
- 在 camera_100ask_dev.c 中,为什么需要图像格式转换?调用了哪个模块?
- 缩放图像时如何保持宽高比?怎么计算缩放后的分辨率?
- 为什么缩放后的图像需要重新申请内存?这块内存是在哪里释放的?
- 解释 camera_100ask_dev 中 thread_camera_work 的主流程和用途?
- 解释 VideoBuf、ConvertBuf、ZoomBuf、FrameBuf 四个缓冲区的用途和关系。
🧠 二、LVGL UI 与交互逻辑
- 如何用 LVGL 创建一个圆形按钮?有哪些必要的 style 设置?
- 如何将摄像头采集到的图像显示到 LVGL 的
lv_img组件中? - 解释 camera_work_timer 是如何触发 UI 刷新的?数据从哪来?
- LVGL 中如何处理滑块(slider)事件?滑动时如何实时更新文本标签?
- 如何使用 LVGL 构建图片浏览器?切换上一张/下一张时如何实现逻辑跳转?
- 为什么要用
pthread_mutex_lock包围图像读取/显示操作? - 如果图像采集和 UI 更新不同步,会出现什么问题?如何解决?
- LVGL 中图片显示为何可能出现“颜色错乱”?和
LV_COLOR_DEPTH有什么关系? - 你是否使用了 LVGL 的动画系统?举一个 UI 动画的应用例子。
- UI 初始化顺序是什么?哪些组件必须先创建?
🧠 三、图像保存与 BMP 文件操作
- BMP 文件格式的头部结构是什么?图像数据在文件中是如何排布的?
- convert_to_bmp_file.c 中如何将 RGB 数据写入文件?注意了哪些对齐或反向问题?
- BMP 保存函数中如何命名图像文件?文件名包含哪些信息?
- 如何实现“拍照”功能?在哪个线程里做了保存操作?
- 你如何避免频繁拍照时出现保存冲突或丢帧?
🧠 四、系统结构、模块封装与线程逻辑
- camera_100ask_dev.c 是如何解耦 UI 和采集逻辑的?好处是什么?
- 列出 camera_100ask_dev 提供的外部 API,它们的职责分别是什么?
- 你是如何控制线程退出、重启或资源释放的?目前设计是否支持热插拔摄像头?
- 如果系统需要支持多个摄像头设备,你会如何扩展
camera_100ask_dev? - 整个系统模块之间的调用关系是怎样的?请画一个简图或描述数据流动路径。