面板要显示当前曲目的专辑封面。封面埋在音频文件的标签里,而我们的曲库横跨三种格式——FLAC、DSF、MP3——意味着三种完全不同的标签结构;封面又是二进制大块数据,现有面板链路的文本行协议装不下它。这篇记录提取与传输两端的完整设计。

一、提取端:三种标签格式一套接口

FLAC:metadata block 里的 PICTURE 块——结构最规整,块头自带 MIME/尺寸/长宽/色深,读到块即得图。

DSF:最绕的一个。DSF 文件头声明的是音频数据长度,ID3v2 标签挂在文件尾部——要先用文件总长减音频长度定位尾部区域,再在尾部找 ID3v2 头。想当然从文件头找标签会直接扑空。

MP3(ID3v2):APIC 帧。帧头带文本编码标记,图片描述前有编码字节——跳过描述找图片数据时编码字节数要对,差一个字节整张图就废了。

三格式解析收拢到一套"取出图片字节 + 宽/高/格式"的接口后面,上层不关心来源。顺带修了一个老 bug:蓝牙电源命令的长度校验写成了恒假条件——strlen 的结果永远非零,跟预想比较的却是别的量。这种"看着像校验、实际从没生效"的代码比没有校验更危险。

二、传输端:0x49 命令 + 24 字节头 + 分块正文

现有面板协议是命令号 + 载荷的帧格式,直接塞一张几百 KB 的图不现实(下位机 FIFO 小、单帧长度有限)。设计成三段:

  1. 24 字节定长头:宽、高、像素格式、总长度、分块数——面板先收到"这张图长什么样";
  2. 正文按块续传:每块带序号,面板端攒齐再解码,中途换歌直接作废重来;
  3. 无图兜底:U 盘无图、DLNA、蓝牙、OTG 四个音源没有封面时,面板统一显示内嵌的品牌默认图——缺数据也要有确定的显示形态,界面上永远不该出现"空白猜不透"的区块。

三、教训:协议枚举必须有单一权威

这个项目面板和主机是两套代码实现同一份私有协议。加 0x49 时发现两边对命令号段的理解已经开始漂移——再各写几轮就会出现"幽灵命令"(一边在发、另一边当未知帧丢弃,还不报错)。

由此立规:命令号枚举的唯一权威在下位机协议头文件,一个命令一行、注释带载荷布局;面板要加命令、改映射,先查这份头文件再动手。文档会过期,头文件里的枚举跟着代码走。

四、工程细节

  • 解析全程静态缓冲、零动态分配——这套代码跑在下位机的常驻服务里,封面是高频路径,不能给内存碎片留机会;
  • 编译静态链接零警告(-Wall -Wextra),大图超出面板预期尺寸时截断推送而不是硬塞;
  • 提取失败(标签损坏/格式野路子)一律走"无图"分支,提取器永不把异常抛给传输层。

小结

“显示一张封面"背后是三层工程问题:容器格式的多样性(提取)、链路的帧大小限制(分块)、双端协议的一致性(枚举权威)。每层都用最朴素的手段收口——统一接口、定长头、单一权威——没有一行聪明的代码,但每一层都堵死了一类未来故障。