# 阶段 1 交接：从“可解码静音”到“能听到正确的声音”

> 当前版本：0.1.1（结构里程碑）。本文件只定义目标、边界和验证顺序，不包含编码器算法实现。

## 1. 起点

已经成立的事实：

- 帧头、边信息、CBR 帧尺寸器和 MSB-first BitWriter 已完成。
- 每帧字节数严格等于 `floor(n * 144 * bitrate / samplerate)` 的累积结果。
- 单声道边信息 17 字节、立体声 32 字节；窗口切换字段布局已按规范实现。
- 任何输入采样目前都被忽略，主数据全零，解码器输出等长静音。
- `moon test --deny-warn` 10 例通过；`verify.py structure` 12 例通过。
- 仓库、CI、mooncakes 已公开，但这些只证明结构正确，不证明音频编码正确。

阶段 1 的目标只有一个：**让真实 PCM 产生可辨认、频谱正确的 MP3 输出**。

测试素材和每个素材的预期主频见 `docs/fixtures.md`；帧解析、频谱和 SNR 的具体命令见 `docs/tooling.md`。

## 2. 阶段 1 完成判据

全部满足才算完成，不能只看“能跑”：

1. `440 Hz` 单声道正弦编码后，独立解码器输出非静音，主频仍在 440 Hz 附近。
2. `sine440_stereo_1s` 的左右声道分别保留 440 Hz / 660 Hz，不串音。
3. `multitone_mono_1s` 的三个分量都能在解码频谱中找到。
4. 单声道和立体声都通过，帧数、解码长度和声道数正确。
5. `mp3dump --strict` 对输出无警告；`part2_3_length` 指示的真实位流长度与写入一致。
6. 连续帧 `main_data_begin` 不越界；阶段 1 可以先固定为 0。
7. 输出仍是精确 128 kbps CBR，帧网格不漂移。
8. `verify.py structure` 不回归；新增的音频用例进入独立验证脚本或 `moon test`。
9. 至少记录一次 SNR 结果，即使数值尚未达到阶段 2 的门槛。

阶段 1 不要求达到正弦 ≥20 dB、音乐 ≥15 dB；那是阶段 2 的质量门槛。

## 2'. 九条判据的实测状态（2026-11-02 深夜）

`main` @ `47da70`，逐条复现；命令与数字如下，全部以 libsndfile（mpg123）解码为准。
长探针走 `sine`（无 payload，39 帧 = 1 s），需要多音的素材走 `encode` 并截 0.18 s
（命令行对 base64 payload 有 ~32 KB 上限）。

| # | 判据 | 实测 | 结论 |
| --- | --- | --- | --- |
| 1 | 440 Hz 单声道非静音、主频仍在该处 | 44928 样本，主频 **440.02 Hz**，峰值 0.574，SNR **21.95 dB** | 通过 |
| 2 | 立体声左右 440/660 不串音 | 左 440.02 / 右 660.01 Hz；串音左→440 **−50.1 dB**、右→660 **−43.6 dB** | 通过 |
| 3 | multitone 三个分量都在 | 437.5 / 880.5 / 1318.0 Hz，相对幅度 0 / **−4.8** / **−12.0 dB**（期望 0/−6/−12） | 通过 |
| 4 | 单/立体声帧数、解码长度、声道数 | 39 帧 → 44928 = 39×1152 样本；声道 1/2 正确；`1152±1` 用例在 selftest | 通过 |
| 5 | `mp3dump --strict` 无警告、位长一致 | 10 个 fixture 全部 strict 通过；编码器内部断言写出位数 == `part2_3_length` | 通过 |
| 6 | `main_data_begin` 不越界 | 全部帧恒为 **0** | 通过 |
| 7 | 精确 128 kbps CBR、帧网格不漂移 | 200 帧共 **83591 字节** = `floor(200·144·128000/44100)`；解码 230400 = 200×1152 | 通过 |
| 8 | `verify.py structure` 不回归、音频用例进脚本 | structure 12/12；`moon test` 29；`selftest.py` 32（含分析层逐线参考与闭环 SNR） | 通过 |
| 9 | 至少记录一次 SNR | 21.95 / 22.46 / 21.97 dB（单/立体声/多音），另有 `sweep_snr.py` 全段表 | 通过 |

已知但不属于这九条的：满幅（99 %）解码峰值 **1.12**，仍有过冲；`global_gain` 一个
granule 一个、无 scalefactor，所以整段 SNR 停在 22–26 dB。两者都是阶段 2 的内容。

## 3. 建议的数据流与边界

```text
interleaved int16 PCM
  -> 多相分析滤波器组（32 子带）
  -> 每个子带 36 点窗、18 点 MDCT（长块）
  -> 每 granule 576 条频率线
  -> scalefactor band 划分与量化
  -> Huffman 编码 + 边信息
  -> bit reservoir / 位流封装
  -> 现有帧写入器（不要改帧头和 CBR 记账）
```

建议保持以下边界，避免把已稳定的结构层卷进算法调试：

| 层 | 责任 | 不应该做的事 |
| --- | --- | --- |
| `src/bits` | MSB-first 写位、字节对齐 | 理解 MP3 语义 |
| `src/mp3/frame` | 帧头、帧大小、padding | 参与量化决策 |
| `src/mp3/sideinfo` | 边信息字段打包 | 计算 Huffman 码长 |
| 新增分析层 | 多相滤波、MDCT、窗 | 直接写帧字节 |
| 新增量化层 | scalefactor、global_gain、量化 | 决定 reservoir 布局 |
| 新增 Huffman 层 | 码表、码字、位计数 | 修改帧头 |
| 新增组装层 | 把 side info + main data 装进帧 | 重新实现帧尺寸器 |

现有 `encode` 入口的输入契约不要变：交织 `Int32`、44100 Hz、1/2 声道、16-bit 范围。内部可以新增模块，但公开 API 保持稳定。

## 4. 竖切顺序

不要按“先写完所有数学，再一次性集成”的方式推进。按可验证的竖切做：

1. **位流自检**：构造已知 Huffman 码字和边信息，确认写入位数、字节对齐、`part2_3_length` 与解析结果一致。
2. **单子带单音**：只让一个子带产生非零数据，确认解码后有对应频率，其他子带接近静音。
3. **单声道 440 Hz**：接入完整分析滤波和 MDCT，确认主频、峰值和长度。
4. **立体声**：确认左右声道独立，边信息和 32 字节布局不串。
5. **多音与瞬态**：加入 multitone、chirp、impulse，观察量化与 Huffman 的边界。
6. **再接 reservoir**：阶段 1 可保持 `main_data_begin = 0`；跨帧复用留到阶段 2。

每一步都必须保留前一步的可复现命令和样本。

## 5. 调试顺序

出现“解码有声但不对”时，按从后往前的顺序排查：

1. **帧层**：`mp3dump --strict` 是否有 warning？帧数、字节数、码率是否仍然正确？
2. **边信息**：`part2_3_length` 与 Huffman 实际写出的位数是否一致？
3. **Huffman**：码表选择、big_values 边界、count1 区域、符号位和 linbits 是否按规范？
4. **量化**：`global_gain`、scalefactor、反量化后的频线是否与 Python 参考一致？
5. **MDCT**：窗口、重叠、符号、频线顺序是否与参考一致？
6. **分析滤波**：32 子带顺序、窗系数、归一化是否一致？

不要先怀疑解码器。先把 `mp3dump`、边信息和位计数对齐，再看波形。

## 6. 必守红线

- **不读取、不移植 LAME 或 shine 的源码**。两者均为 LGPL；只能依据 ISO/IEC 11172-3 和明确许可的数据表实现。
- **不把测试 oracle 当实现来源**。libsndfile/mpg123 只用于解码验证，不能从中复制编码逻辑。
- **不修改已发布的 0.1.x 帧布局来“顺手优化”**。帧层回归会让所有结构测试失效。
- **不在阶段 1 开短块、联合立体声、VBR、心理声学模型**。先把长块 CBR 做对。

## 7. 交接时应该留下来的东西

- 一份能复现的 440 Hz 单声道命令和输出文件。
- 一份 `mp3dump --json` 报告，证明边信息和帧网格。
- 一份 `snr.py` 结果，记录参考文件、解码器、延迟和 trim。
- 一份 `spectrum.py` 结果，证明主频和声道没有错位。
- 一条 `verify.py structure` 仍然全过的记录。
- 新增模块的公开接口和“暂不支持”的边界说明。

## 8. 现在还不要做

- 不要报名初审。
- 不要发布 0.2.0，除非阶段 1 的完成判据全部满足。
- 不要为了“看起来功能多”而加短块或 VBR。
- 不要把静音测试通过当成音频编码通过。
