# sys.mediaFoundation.mp3Decoder

用 Windows 自带的 MP3 解码器（Media Foundation `IMFTransform`），把内存中的 MP3 字节增量解码为 16 位 PCM。

- 输入可以任意切块，不必按 MP3 帧对齐。收到一部分就能得到一部分 PCM，不必等待完整文件。
- 只负责解码：不播放、不读文件、不联网。播放用 [sys.waveOut](https://www.aardio.com/zh-cn/docs/library/sys/waveOut/)；边收边播可以直接用 [sys.mediaFoundation.streamingMp3Player](https://www.aardio.com/zh-cn/docs/library/sys/mediaFoundation/streamingMp3Player/)。
- 同步调用，不需要消息循环，工作线程里也可以用。

## 增量解码

```aardio
import sys.mediaFoundation.mp3Decoder;

var decoder,err = sys.mediaFoundation.mp3Decoder({sampleRate=24000;channels=1});
if(!decoder) return print(err);

var mp3 = string.loadBuffer("/speech.mp3");
var chunks = {};
for(first=1;#mp3;4096){ //模拟逐块收到的数据，块大小任意
	var pcm,err = decoder.decode(raw.slice(mp3,first,math.min(first+4095,#mp3)));
	if(!pcm) return print(err);
	table.append(chunks,pcm); //空数组表示暂时没有输出
}
var tail,err = decoder.finish(); //输入结束，取出剩余的 PCM
if(!tail) return print(err);
table.append(chunks,tail);
decoder.close();

var pcm = raw.join(chunks); //int16 交错 PCM
print(#pcm,decoder.format.sampleRate,decoder.format.channels);
```

解码得到的 PCM 可以交给其他模块：

```aardio
import sys.mediaFoundation.mp3Decoder;
import sys.waveOut;
import av.audio;

var decoder = sys.mediaFoundation.mp3Decoder({sampleRate=24000;channels=1});
var chunks = decoder.decode(string.loadBuffer("/speech.mp3"));
if(chunks) table.append(chunks,decoder.finish());
decoder.close();

//播放
var out = sys.waveOut(decoder.format);
for(i=1;#chunks) out.write(chunks[i]);
out.wait();
out.close();

//转为 av.audio 音频片段，用于混音、分析等
var clip = av.audio.fromPcm16(raw.join(chunks),decoder.format.channels,decoder.format.sampleRate);
```

## 接口

| 接口 | 说明 |
|---|---|
| `sys.mediaFoundation.mp3Decoder(options)` | 创建解码器。`options` 必须指定 MP3 的实际 `sampleRate` 和 `channels`（1 或 2）。失败返回 `null,err` |
| `decode(data)` | 解码一段 MP3（字符串或 buffer），返回本次得到的 PCM 块数组。**空数组表示暂时没有输出**，不是错误，也不是结束。失败返回 `null,err` |
| `finish()` | 输入结束，排空解码器，返回剩余的 PCM 块数组。重复调用返回空数组；之后不能再 `decode` |
| `close()` | 释放解码器，可以重复调用。已经返回的 PCM 不受影响 |
| `format` | `{codec="pcm";sampleRate;channels;bitsPerSample=16}`，可以直接传给 `sys.waveOut` |
| `inputBytes` / `pcmBytes` | 已送入的 MP3 字节数／已输出的 PCM 字节数 |
| `ended` | 是否已调用 `finish` |
| `error` | 失败信息。失败后 `decode`、`finish` 都返回这个错误，请关闭后重新创建 |

PCM 块都是独立的 buffer，可以用 `raw.join` 合并。

## 格式参数必须与 MP3 一致

`sampleRate` 只能是 MP3 的采样率之一：8000、11025、12000、16000、22050、24000、32000、44100、48000。输出是同采样率、同声道数的 PCM，参数不是重采样设置。

**解码器不探测、也不检查 MP3 的实际格式**。系统解码器对参数不一致不报错，实测结果是：

- 采样率不一致：得不到任何 PCM（`decode` 和 `finish` 都只返回空数组）。
- 声道数不一致：得到的是错误的 PCM。

所以只在已知格式时使用，例如 Edge TTS 的 MP3 是 24kHz、单声道。格式未知的 MP3 文件，可以先用 `sys.mediaFoundation.reader` 读取 `reader.info.audio`，或者直接用 reader 解码。

其他行为：

- 一个实例处理一条连续的 MP3，不能把几个独立的 MP3 拼起来当作播放列表。
- 无法解码的字节会被跳过：MP3 前面混入的无效字节不影响解码；整段都不是 MP3 时（例如 HTML 错误页），得不到任何 PCM，但不报错。可以在 `finish` 后检查 `pcmBytes` 是否为 0。
- 在创建解码器的线程中使用。Windows N/KN 版未安装媒体功能包时没有系统 MP3 解码器，创建会失败并返回错误信息。

## 与 sys.mediaFoundation.reader 的区别

- `reader` 读取本地文件，支持多种格式和定位（`seek`），适合处理完整的媒体文件。
- `mp3Decoder` 接收内存中持续到达的 MP3 字节，适合网络流、TTS 等边收边处理的场景。
