# sys.waveOut

把 16 位 PCM 送往系统默认播放设备（Windows waveOut）。

- 只接收 PCM，不解码。MP3 先用 [sys.mediaFoundation.mp3Decoder](https://www.aardio.com/zh-cn/docs/library/sys/mediaFoundation/mp3Decoder/) 解码；WAV 等文件可以用 [sys.mediaFoundation.reader](https://www.aardio.com/zh-cn/docs/library/sys/mediaFoundation/) 读出 PCM。
- `write` 立即返回，设备在后台按写入顺序播放。暂时没有数据时设备静音等待，继续写入就接着播。
- 不需要窗口，没有回调。`wait()` 等待期间会处理窗口消息。
- 边收边播 MP3 可以直接用 [sys.mediaFoundation.streamingMp3Player](https://www.aardio.com/zh-cn/docs/library/sys/mediaFoundation/streamingMp3Player/)，它就是用 `mp3Decoder` 加 `sys.waveOut` 实现的。

## 播放生成的声音

```aardio
import sys.waveOut;

var out,err = sys.waveOut({sampleRate=24000;channels=1});
if(!out) return print(err);

//0.5 秒 440Hz 正弦波，16 位单声道
var samples = {};
for(i=1;12000) samples[i] = math.floor(math.sin(2*math.pi*440*(i-1)/24000) * 8000);
out.write(raw.buffer({word s[] = samples}));

out.wait(); //等待播放完
out.close();
```

## 播放 WAV 文件

WAV 是容器格式，里面有格式块、数据块，还可能有其他块，不能把整个文件当作 PCM 写入，也不能简单跳过固定的 44 字节。可以先用 `sys.mediaFoundation.reader` 读出 PCM 和格式：

```aardio
import sys.mediaFoundation.reader;
import sys.waveOut;

var reader,err = sys.mediaFoundation.reader("/sound.wav");
if(!reader) return print(err);
var audio = reader.info.audio;
var pcm = reader.readAudio(); //int16 交错 PCM
reader.close();

var out,err = sys.waveOut({sampleRate=audio.sampleRate;channels=audio.channels});
if(!out) return print(err);
out.write(pcm);
out.wait();
out.close();
```

## 播放解码得到的 PCM

`sys.mediaFoundation.mp3Decoder` 的 `format` 字段可以直接作为参数：

```aardio
import sys.mediaFoundation.mp3Decoder;
import sys.waveOut;

var decoder,err = sys.mediaFoundation.mp3Decoder({sampleRate=24000;channels=1});
if(!decoder) return print(err);
var out,err = sys.waveOut(decoder.format);
if(!out) return print(err);

var chunks = decoder.decode(string.loadBuffer("/speech.mp3"));
if(chunks) table.append(chunks,decoder.finish());
decoder.close();
for(i=1;#chunks) out.write(chunks[i]);

out.wait();
out.close();
```

## 接口

| 接口 | 说明 |
|---|---|
| `sys.waveOut(options)` | 打开默认播放设备。`options` 必须指定 `sampleRate`（Hz）和 `channels`（1 或 2）；可选 `volume`（0～1，默认 1），`bitsPerSample` 只能是 16。失败返回 `null,err` |
| `write(pcm)` | 追加 16 位交错 PCM（立体声为 L,R,L,R…），字符串或 buffer，长度必须是 `channels×2` 的整数倍。数据会复制，返回后可以复用或修改原 buffer。成功返回 `true`，失败返回 `null,err` |
| `poll()` | 刷新 `completedBytes` 和 `playedSeconds`。不调用也会正常播放，只是这两个属性不更新 |
| `wait(timeoutMs)` | 等待已写入的数据全部播完，期间处理窗口消息。省略 `timeoutMs` 则一直等待；超时只结束等待，不停止播放 |
| `close()` | 立即停止，释放设备和全部 PCM，可以重复调用 |
| `format` | `{codec="pcm";sampleRate;channels;bitsPerSample=16}` |
| `volume` | 软件音量 0～1，修改后只影响之后写入的数据；不改系统音量 |
| `totalBytes` / `completedBytes` | 已提交给设备、已播完的 PCM 字节数。两者相等表示已写入的数据全部播完 |
| `playedSeconds` | 设备报告的播放位置（秒），不是扬声器的声学测量 |
| `handle` | waveOut 设备句柄，`close` 后为 `null` |

## 注意事项

- **写入的 PCM 一直保留到 `close`**，不逐块回收。设备异步读取这些内存，`close` 先停止设备、撤销缓冲，再释放内存。aardio 是 32 位进程，播放长音频时要留意内存：24kHz 单声道每分钟约 2.7MB，44.1kHz 立体声每分钟约 10MB。
- 只支持 16 位 PCM、单声道或立体声。本库不做重采样；使用系统默认播放设备（WAVE_MAPPER），设备不直接支持的采样率由系统转换。
- 所有方法在创建对象的线程调用。
- Waveform Audio 是 Windows 较早的接口，微软建议新项目考虑 WASAPI。对“已经有 PCM，按顺序播放”这类简单场景，它简单可靠；本库不承诺比其他接口延迟更低。
- 声音经过本进程的音频会话。`playedSeconds` 正常前进却听不到声音时，用 `sys.audioVolume.session()` 检查本进程是否在音量合成器里被静音，详见 [流式 MP3 播放](https://www.aardio.com/zh-cn/docs/library/sys/mediaFoundation/streamingMp3Player/) 的「播放无声排查」。
