转换模块(十二)实现 RGB 转 RGB 项目整合与上机实验前置知识必须先理解 [10-转换模块 结构体抽象与管理](10-转换模块 结构体抽象与管理.md) 中定义的T_VideoConvert框架已掌握 11-MJPEG 转 RGB 的完整流程理解像素格式RGB56516 位、RGB3232 位本文是本系列最后一篇代码实现篇完成后整个 video2lcd 项目全部打通。 本节核心目标理解 RGB 转 RGB 模块的作用摄像头输出 RGB565 但 LCD 需要 RGB32 时进行位扩展转换掌握 RGB565 → RGB32 的转换算法移位、左移、拼装理解相同格式时不会调用转换模块main 逻辑决定完整运行整个 video2lcd 项目编译 → 传到开发板 → 执行排查常见问题设备节点错误、格式不支持等 本节涉及的文件文件作用convert/rgb2rgb.cRGB565 → RGB32 转换实现本节核心main.c主循环采集 → 转换 → 缩放 → 合并 → 显示Makefile,Makefile.build编译系统include/config.h调试开关设备路径video/v4l2.c摄像头采集display/fb.cFramebuffer 显示render/operation/zoom.c画面缩放render/operation/merge.c画面合并netprint_client.c网络打印调试工具可选其余模块video 框架、display 框架、yuv2rgb、mjpeg2rgb均已在 06~11 中掌握。 一、为什么需要 RGB 转 RGB1.1 场景分析摄像头可能直接输出RGB565格式例如某些 OV 系列传感器但 LCD 显示可能配置为RGB320x00RRGGBB每像素 4 字节。场景摄像头格式LCD 格式是否需要转换1RGB565RGB565❌ 不需要格式相同2RGB565RGB32✅ 需要转换3RGB32RGB32❌ 不需要注意rgb2rgb 模块也支持输入输出均为 RGB565 的情况直接memcpy但 main.c 中已经判断格式相同就跳过所以实际不会调用。1.2 为什么不直接支持 RGB32 格式摄像头直接输出 RGB32 的场景很少见因为 RGB32 占用带宽太大640×480 下每帧 1.2MBUSB 带宽扛不住。大多数摄像头输出 MJPEG压缩或 YUYV原始或 RGB565原始。 二、RGB565 → RGB32 转换详解2.1 RGB565 与 RGB32 的位宽差异格式红色R绿色G蓝色B总位数每像素字节RGB5655 位6 位5 位16 位2 字节RGB328 位8 位8 位32 位4 字节内存布局对比RGB565 (2字节): [R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0] ↑ 5位红 ↑ 6位绿 ↑ 5位蓝 RGB32 (4字节): [00000000 R7~R0 G7~G0 B7~B0] ↑ 空(8位) ↑ 8位红 ↑ 8位绿 ↑ 8位蓝2.2 转换步骤从 16 位 RGB565 像素中提取 R、G、B 分量分别扩展到 8 位再拼成 32 位整数步骤1: 从 RGB565 提取分量 r (color 11) → 取出高 5 位红 g (color 5) 0x3f → 取出中间 6 位绿 b color 0x1f → 取出低 5 位蓝 步骤2: 扩展到 8 位 R8 r 3 ← 5 位 → 8 位左移 3 位 G8 g 2 ← 6 位 → 8 位左移 2 位 B8 b 3 ← 5 位 → 8 位左移 3 位 步骤3: 拼成 32 位 color32 (R8 16) | (G8 8) | B8图示RGB565 16位: RRRR RGGG GGGG BBBBB │ │ │ ▼ ▼ ▼ │ │ │ 左移3位 左移2位 左移3位 │ │ │ ▼ ▼ ▼ RGB32 32位: 00000000 RRRRR000 GGGGGG00 BBBBB000 (高8位0) (红8位) (绿8位) (蓝8位)2.3 为什么绿色移位不同分量原始位数目标位数丢失信息左移量解释红®5 位8 位丢失 3 位3r 3低位补 0绿(G)6 位8 位丢失 2 位2g 2低位补 0蓝(B)5 位8 位丢失 3 位3b 3低位补 0因为绿色原始有6 位比红色/蓝色多 1 位丢失的信息少所以只需要左移2 位就能铺满 8 位而红/蓝丢失 3 位需要左移3 位。记忆口诀红蓝移 3绿色移 2因为绿色多 1 位少补 1 个 0。2.4 完整转换代码for(y0;yptPixelDatasOut-iHeight;y){for(x0;xptPixelDatasOut-iWidth;x){unsignedshortcolor*pwSrc;// 读一个 RGB565 像素// 1) 提取分量unsignedintrcolor11;// 高5位 → 红unsignedintg(color5)0x3f;// 中间6位 → 绿unsignedintbcolor0x1f;// 低5位 → 蓝// 2) 位扩展并拼成 32 位unsignedintrgb32((r19)|(g10)|(b3));// 等价写法((r3)16) | ((g2)8) | (b3)*pdwDestrgb32;// 写入目标缓冲区}} 三、模块注册与框架3.1 T_VideoConvert 结构体rgb2rgb.c 中定义的转换器实例staticT_VideoConvert g_tRgb2RgbConvert{.namergb2rgb,.isSupportisSupportRgb2Rgb,.ConvertRgb2RgbConvert,.ConvertExitRgb2RgbConvertExit,};3.2 isSupport 判断逻辑staticintisSupportRgb2Rgb(intiPixelFormatIn,intiPixelFormatOut){if(iPixelFormatIn!V4L2_PIX_FMT_RGB565)return0;// 输出支持 RGB565 或 RGB32if((iPixelFormatOutV4L2_PIX_FMT_RGB565)||(iPixelFormatOutV4L2_PIX_FMT_RGB32))return1;return0;}3.3 Convert 函数分支处理staticintRgb2RgbConvert(PT_VideoBuf ptVideoBufIn,PT_VideoBuf ptVideoBufOut){if(ptVideoBufOut-iPixelFormatV4L2_PIX_FMT_RGB565){// 场景输出 RGB565同格式复制memcpy(ptPixelDatasOut-aucPixelDatas,ptPixelDatasIn-aucPixelDatas,ptPixelDatasOut-iTotalBytes);return0;}elseif(ptVideoBufOut-iPixelFormatV4L2_PIX_FMT_RGB32){// 场景输出 RGB32位扩展转换// ... 上面 2.4 节的循环代码 ...return0;}return-1;}3.4 注册到管理器intRgb2RgbInit(void){returnRegisterVideoConvert(g_tRgb2RgbConvert);}注册后VideoConvertInit()会将其挂入全局链表供GetVideoConvertForFormats()按格式匹配。 四、上机实验4.1 实验目的在 ARM 开发板上完整运行 video2lcd 项目验证从摄像头采集到 LCD 显示的整条链路。4.2 编译项目在 Ubuntu 主机上进入 video2lcd 目录执行 makebook100ask:~/02_video2/video2lcd/video2lcd$make你会看到每个.c文件被arm-linux-gcc编译最后链接成video2lcd可执行文件。编译失败排查确保已安装交叉编译工具链arm-linux-gcc -v确保已安装 libjpeg 的交叉编译版本用于 MJPEG 解码报错undefined reference to jpeg_*→ 缺少-ljpeg4.3 上传到开发板# 方法1adb推荐book100ask:~/02_video2/video2lcd/video2lcd$ adb push video2lcd /root/# 方法2scp网络book100ask:~/02_video2/video2lcd/video2lcd$scpvideo2lcd root192.168.1.100:/root/# 方法3nfs网络文件系统book100ask:~/02_video2/video2lcd/video2lcd$cpvideo2lcd /nfs_root/4.4 运行实验4.4.1 实验记录━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 实验记录请填写 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 开发板型号___100ask_IMX6ULL或你的具体型号___ LCD 分辨率___程序自动获取例如 800x480___ 摄像头型号___USB 摄像头支持 YUYV 格式___ 摄像头节点___/dev/video1___ 命令 $ ./video2lcd /dev/video1 程序输出日志 ────────────────────────────────────────────────── /dev/video1 supports streaming i/o Convert yuv2rgb, ret 0 Convert yuv2rgb, ret 0 Convert yuv2rgb, ret 0 ...后续每帧均打印 Convert yuv2rgb, ret 0 ────────────────────────────────────────────────── 摄像头实际输出格式___V4L2_PIX_FMT_YUYV___ 匹配的转换模块___yuv2rgb___ 画面显示效果____正常显示 _______ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━摄像头实验结果展示linux相机4.4.2 参考成功输出/dev/video1 supports streaming i/o Convert yuv2rgb, ret 0 Convert yuv2rgb, ret 0 ...说明摄像头输入格式是 YUYV自动匹配了yuv2rgb模块LCD 显示正常。4.4.3 参考失败输出/dev/video0 is not a video capture device VideoDeviceInit for /dev/video0 error!这是因为/dev/video0可能不是摄像头设备如板载 ISP 或其他需要换到/dev/video1。提示/dev/video1通常是 USB 摄像头节点/dev/video0可能是板载摄像头或其他设备。4.5 观察现象屏幕上应实时显示摄像头画面画面在屏幕中央等比例显示按CtrlC终止程序 五、常见问题排查现象可能原因解决方法no devices/emulators foundadb 服务未连接重新插拔 USB检查adb devicesis not a video capture device设备节点错误用ls /dev/video*查看可用节点换一个试试Convert ... ret -1格式不匹配或解码失败检查摄像头输出格式是否支持 YUYV/MJPEG/RGB565画面花屏或颜色错误转换公式或字节序错误检查 RGB565 打包顺序小端/大端程序卡死、poll 超时忘记 QBUF 或驱动异常确保GetFrame和PutFrame成对调用编译报错undefined reference to jpeg_*未链接 libjpeg确认 Makefile 中LDFLAGS包含-ljpeg编译报错arm-linux-gcc: not found交叉编译器未安装安装交叉编译工具链检查$PATH✅ 六、全系列知识点自查清单以下内容覆盖 06~12 所有必须掌握的点。请逐条自问是否理解摄像头模块06~09结构体与框架06struct VideoOpr和struct VideoDevice的作用和区别链表注册RegisterVideoOpr的尾插法逻辑VideoDeviceInit如何遍历链表并调用InitDevice为什么需要统一的设备管理框架解耦、可扩展V4L2 初始化07~086 步初始化流程open → QUERYCAP → ENUM_FMT → S_FMT → REQBUFS → mmap → QBUFVIDIOC_S_FMT后必须读回实际宽高的原因驱动可能调整VIDIOC_REQBUFS的 count 可能小于请求值程序要兼容mmap参数PROT_READ,MAP_SHARED的含义VIDIOC_QBUF的作用及必须在STREAMON之前完成数据传输09poll等待数据就绪的必要性避免忙等VIDIOC_DQBUF取出已填满缓冲区保存 indexVIDIOC_QBUF归还缓冲区形成循环VIDIOC_STREAMON/STREAMOFF控制流启停GetFrame返回的aucPixelDatas指向mmap 内存零拷贝PutFrame必须在数据处理之后调用转换模块10~12转换框架10T_VideoConvert与T_VideoOpr的相似设计模式RegisterVideoConvert链表注册GetVideoConvertForFormats按格式匹配VideoConvertInit批量注册 yuv2rgb, mjpeg2rgb, rgb2rgbYUV 转 RGB未单独成篇但在 10 中有涉及YUYV 的 4 字节 → 2 像素排列U/V 共享查表法原理预计算整数表避免浮点RGB565 打包红5绿6蓝5小端存储时的两字节写入顺序内存管理输出缓冲区第一次 malloc之后复用MJPEG 转 RGB11libjpeg 解码的 8 步固定顺序为什么必须自定义错误处理setjmp/longjmp为什么必须用内存数据源jpeg_mem_src_tj解码后 RGB24 → RGB565/RGB32 的再次转换RGB 转 RGB12本文RGB565 → RGB32 的位扩展公式为什么绿色移位 2 位红蓝移位 3 位相同格式时 main 不会调用转换模块主程序整合main 函数流程显示初始化 → 摄像头初始化 → 格式匹配 → 启动流 → 循环取帧 → 转换 → 缩放 → 合并 → 刷新缩放居中算法保持比例停止流并释放资源ExitDevice,ConvertExit在程序退出时调用编译与调试使用make编译跨平台 ARM 程序使用adb push上传到开发板使用adb shell进入板子运行观察打印信息判断错误点❓ 七、最终自测题一问一答Q1: 如果摄像头输出 RGB565LCD 也是 RGB565程序会调用 rgb2rgb 模块吗为什么A1: 不会。因为 main 中先判断iPixelFormatOfVideo iPixelFormatOfDisp相等时直接使用原始数据不进入转换分支。Q2: 在 RGB565 → RGB32 转换中为什么绿色要左移 2 位而红蓝左移 3 位A2: 因为绿色原始占 6 位需要补 2 位到 8 位红蓝原始占 5 位需要补 3 位。左移的位数等于需要补充的低位零的个数。Q3: 使用 libjpeg 解码 MJPEG 时如果不设置自定义错误处理当遇到损坏的 JPEG 帧时会发生什么A3: libjpeg 默认调用exit()整个程序会崩溃退出。Q4:VIDIOC_DQBUF返回后为什么要保存 index 到ptVideoDevice-iVideoBufCurIndexA4: 因为后续PutFrame必须归还同一个缓冲区需要知道是哪个索引。Q5: 为什么 yuv2rgb 模块中要用查表法而不是直接用浮点公式A5: 浮点运算在嵌入式设备上很慢查表法用整数数组预计算速度快几十倍。Q6: 执行./video2lcd /dev/video1后屏幕无图像打印Convert ret -1可能是什么原因A6: 可能原因摄像头输出格式不是程序支持的三种格式之一YUYV/MJPEG/RGB565或者GetVideoConvertForFormats找不到匹配的转换模块。Q7: 程序运行几秒后卡死不再打印新帧最可能是什么问题A7: 可能是在循环中忘记调用 PutFrame导致所有缓冲区都被 DQBUF 借出驱动没有可用缓冲区poll 永远等待。检查GetFrame和PutFrame是否配对调用。Q8: 缩放模块PicZoom使用的是什么算法有什么优缺点A8: 使用最近邻插值Nearest Neighbor。优点速度快、无浮点运算缺点缩放后图像可能有锯齿。Q9:mmap的MAP_SHARED和MAP_PRIVATE有什么区别A9:MAP_SHARED对映射内存的修改会写回设备帧缓冲场景必须用MAP_PRIVATE是写时复制修改不影响原设备。Q10: 如果 LCD 屏幕是 800×480摄像头输出是 640×480程序会进行缩放吗A10: 不会。因为 640 800 且 480 480画面不比屏幕大不需要缩放直接居中显示。 八、完整代码结构回顾video2lcd/ ├── main.c # 主程序主循环 ├── Makefile # 顶层编译 ├── Makefile.build # 递归编译模板 │ ├── include/ # 头文件接口定义 │ ├── config.h # 全局宏FB设备名、调试打印开关 │ ├── disp_manager.h # 显示设备接口 │ ├── video_manager.h # 摄像头设备接口 │ ├── convert_manager.h # 格式转换接口 │ ├── pic_operation.h # 像素数据结构 T_PixelDatas │ └── render.h # 缩放/合并函数声明 │ ├── video/ # 摄像头模块 │ ├── video_manager.c # 链表管理注册、查找、初始化 │ └── v4l2.c # V4L2 具体实现 │ ├── display/ # 显示模块 │ ├── disp_manager.c # 显示设备管理器 │ └── fb.c # Framebuffer 操作 │ ├── convert/ # 格式转换模块 │ ├── convert_manager.c # 转换管理器 │ ├── yuv2rgb.c # YUYV → RGB │ ├── color.c color.h # YUV↔RGB 查找表 │ ├── mjpeg2rgb.c # MJPEG → RGB │ ├── jdatasrc-tj.c # libjpeg 内存数据源 │ ├── rgb2rgb.c # RGB565 → RGB32 │ ├── jpeglib.h / jerror.h / jinclude.h # libjpeg 头文件 │ ├── render/ # 渲染模块 │ └── operation/ │ ├── zoom.c # 图片缩放 │ └── merge.c # 图片合并 │ └── netprint_client.c # 网络打印调试可选 总结通过本系列博客06~12你已经完成了一个完整的嵌入式 Linux 相机项目掌握了能力掌握内容V4L2 设备框架打开、设置、采集、释放完整流程模块化设计链表注册 函数指针多态 策略模式图像格式转换YUV→RGB、MJPEG→RGB、RGB→RGB 三种图像渲染最近邻缩放 区域合并 居中显示嵌入式编译交叉编译、adb 部署、开发板运行调试能力日志分析、设备节点排查、常见问题定位现在你应该能够独立编写类似的视频采集程序甚至扩展支持更多格式或特效。下一节预告13-全项目架构与代码运转流程 将从架构高度俯瞰整个项目的设计思路和数据流转。