C++11 + Qt5 的本地音乐播放器。sdk/ 负责解码、重采样、设备输出与元数据提取,app/ 是桌面界面。
本文件只收录这个项目自己的行话;通用编程概念不进来。
时长: 一段音频真正有多长,语义只有一个。它有两个具体来源:标称时长与解码时长,两者可以不同源,但不能不同义。
标称时长:
提取器上报的 durationMs,播放前就能拿到,UI 与 seek 依赖它。按可精确度分档取值(精确换算 > 时间轴推导 > 码率估算 > 容器时间戳),所以它的准确度随格式变化。
Avoid: 「容器时长」——那只是它最差的一档,不是它本身
容器声明时长:
容器或文件头里写着的时长字段。可能来自码率估算,也可能是脏数据(仓库里的 music-mkv.mkv 把采样数当毫秒写)。只在没有更好来源时才成为标称时长。
Avoid: 「真实时长」「实际时长」
解码时长: 把 PCM 全解出来后按采样率折算出的长度,是时长的权威测量,只给断言用。 Avoid: 「解码后时长」(容易被读成「解码这一步花的时间」)
受支持格式: 能解出与标称时长一致的 PCM,且被覆盖度断言锁定的格式。注册表里注册了、枚举里写了,都不等于受支持。 Avoid: 「已实现格式」「支持列表」
覆盖度: 解码出的 PCM 折算成的时长与标称时长的吻合程度。判据是双向的:短了是截断,长了是重复解码。 Avoid: 「解码成功率」「完成度」
包: 解复用器给出的一个帧边界及其负载,是音频负载的唯一表示;整块视图只是按需拼合的产物。 Avoid: 「数据块」「buffer」
提取器: 按格式拆分的负载与元数据来源,负责给出 spec、标称时长和包边界。 Avoid: 「解析器」——parser 是 ffmpeg 里码流层的切帧器,和提取器不是一回事
边解边播: 一种数据供给契约:解码与消费并行推进,播放期间为当前曲目持有的音频数据量不随素材时长增长。它不是一种可切换的播放模式,也不表达音频来源是否来自网络。 Avoid: 「流式」「流式播放」「流式解码」「网络流」
播放状态: 播放器对外呈现的三种状态之一:正在播放、已暂停、已停止。它只回答「此刻是否在消费音频」,不回答解码进度、错误原因或音频来源。 Avoid: 「播放任务状态」「播放模式」「流式状态」
播放任务状态: 一次播放任务内部的进度状态(空闲 / 解码中 / 定位中 / 已到末尾 / 出错),只供 SDK 内部判断静默窗口、取消时机与 EOS 汇总,不对外呈现。 Avoid: 「播放状态」「解码状态」
四类坏法 + 一条结论。完整规格在 doc/bad-input-spec.md。
截断: 文件在尾部被截短,声明的长度大于实际可读字节数。处置是「能解出多少就播多少」,拒绝只是兜底。 Avoid: 「文件不完整」——那是给用户看的提示文案,不是现象的名字
坏帧:
流内某个帧坏了,但它之后还有完好的数据。处置是跳过这段继续播,最后汇总一条 DataCorrupted。
判别原则只有一句:坏帧之后还有数据,截断之后没有。
Avoid: 「损坏」「坏文件」——把四类坏法混成一个词,处置就没法分
头坏:
头部或必需结构自身不自洽:读不出结构、读出的值违反格式硬不变量(sampleRate / numChannel 必须 > 0)、
或解析不终止。不需要看音频数据就能判死,处置是拒绝。
Avoid: 「格式不支持」——那是扩展名与魔数都认不出的情况(ExtractorUnsupported)
容器声明撒谎: 头部自洽,但字段值与实际数据不符(时长写大、采样率写错)。必须解码才能发现, 而且只有「有独立时长字段」的格式才判得出。判据是差分不是阈值:改掉那个字段,标称时长与解出时长必须逐位不变。 Avoid: 「元数据错误」——这里的 meta 指容器字段,与音频标签无关,本项目根本没有标签 API
不可检测: 该格式的数据里没有第二个独立来源可供比对,这类坏法在字节层面不可判。 它是结论不是待办:写进规格、锁在反向用例里,不许用时长旁证伪造检测。 Avoid: 「暂不支持检测」——听起来像排期,实际是数据格式的极限
观测仪表:
测试进程内由 StreamingBudget 套件经 MESSAGE(...) 输出的字段,如分配记账峰值、稳态静音填充次数、start→首帧耗时、init 扫描耗时。只描述一次测试运行内部的量,不参与判定。
Avoid: 「指标」「判据」——前者指生产链的量,后者是可红可绿的断言
独立指标:
生产链日志记录的一次性耗时或状态量(如打开耗时、切歌耗时),以及口径已定义但取数点按裁决不落地的量(如端到端 drift,见 doc/observability-contract.md §4.4 / §8.3)。与观测仪表分属两条取数路径,禁止跨源相减或比较。
Avoid: 「观测仪表」「判据」「尚未落地」——后者听起来像排期,实际是已裁决不落地(#105)且永久不设数值阈值(#104)
契约漂移: 公开 API 的签名、取值集合或事件序列相对其规格的可观察偏离,跨版本或跨运行。不指内部状态写入时机、缓冲水位等实现细节,也不指单次偶发测试失败。 Avoid: 「回归缺陷」「文档措辞差异」
播放时钟与源时钟: 源时钟是当前已交付 PCM 对应的源时间轴位置;播放时钟是消费端累计 PCM 换算的播放进度。两者都不等于墙钟,也不等于「已听到的声音」——取的是已消费。恒定差额是偏置,只有随播放推进的变化量才叫 drift。 Avoid: 「墙钟」「端到端延迟」
完整口径见 doc/observability-contract.md。