Windows 原生流式 MP3 播放器,边收边播:MP3 字节 → sys.mediaFoundation.mp3Decoder → 16 位 PCM → sys.waveOut。
音频数据在内存中处理,不写临时文件,不依赖 BASS、浏览器、OCX 或音频 HTTP 服务。可以直接接收 TTS 的音频,也可以接收应用从网络取得的 MP3 字节。需要自己处理 PCM 时,直接用它的两个组成模块。
import sys.mediaFoundation.streamingMp3Player;
import web.edgeTextToSpeech;
var player,err = sys.mediaFoundation.streamingMp3Player();
if(!player) return print(err);
var engine = web.edgeTextToSpeech({voice="zh-CN-XiaoxiaoNeural"});
// 播放器直接作为输出对象:音频交给 write,成功调用 finish(),失败调用 finish(错误信息)
var task,err = engine.synthesize({text="这是原生流式语音,收到前面的音频即可开始播放。"},player);
if(!task){player.close();return print(err.message);}
var ok,err = player.wait(120000); // 等待真正播放完毕,期间处理窗口消息;合成失败也会返回错误
if(!ok) print(err);
task.cancel(); // 提前结束等待时停止合成;已结束的任务忽略此调用
player.close();
带输入框、播放状态及停止按钮的完整示例:~/examples/Media/Audio/edgeTTS-mediaFoundation.aardio。界面程序的写法:
import win.ui;
import web.edgeTextToSpeech;
import sys.mediaFoundation.streamingMp3Player;
var winform = win.form(text="朗读");
var engine = web.edgeTextToSpeech();
var task,player;
// 唯一的清理入口:停止按钮、关闭窗口、播放结束、出错都调用它,可以重复调用
var stop = function(){
if(task) task.cancel();
if(player) player.close();
task,player = null,null;
}
var speak = function(text){
stop();
var err;
player,err = sys.mediaFoundation.streamingMp3Player({
onEnd = function(){ stop(); };
onError = function(err){ stop(); winform.msgboxErr(err); }; // 合成失败也从这里报告
});
if(!player) return winform.msgboxErr(err);
task,err = engine.synthesize({text=text},player);
if(!task){ stop(); winform.msgboxErr(err.message); }
}
winform.onClose = function(){ stop(); }
取消和关闭之后,旧的任务与播放器都不会再回调,所以不需要额外的状态或计数变量。
import sys.mediaFoundation.streamingMp3Player;
var player,err = sys.mediaFoundation.streamingMp3Player({
sampleRate=24000;
channels=1;
volume=1;
maxPcmBytes=67108864;
onStart=function(){ /*首次提交 PCM 到设备*/ };
onEnd=function(){ /*余音已经播完,资源已释放*/ };
onError=function(err){ print(err); };
});
if(!player) return print(err);
// 在之后的 MP3 数据回调中调用 player.write(data)。
// 全部输入成功结束:player.finish()。
// 来源失败:player.finish("错误信息")。
// 立即停止或窗口退出:player.close()。
player.close();
| 接口 | 语义 |
|---|---|
write(data) |
追加 MP3 string/buffer,成功 true,失败 null,err。空块无操作,不代表 EOF。同步复制和解码,播放异步进行,调用返回后可复用输入 buffer |
finish() |
输入结束,排空解码器,继续播放已提交的 PCM。幂等,不立即停止 |
finish(err) |
来源失败,立即中止并触发一次 onError;err 可以是错误字符串或含 message 的错误表 |
close() |
立即停止,释放解码器、声卡、内存及内部定时器。幂等,不触发 onEnd |
wait(timeoutMs) |
消息泵等待播放结束;成功 true,失败 null,err。超时仅停止等待,不停止播放 |
state |
buffering、playing、draining、ended、failed、closed |
done / inputEnded |
播放终结/输入结束,两者不是同一件事 |
receivedBytes / pcmBytes |
已送入解码器的 MP3 字节数/已提交声卡的 PCM 字节数 |
playedSeconds |
设备报告的播放位置,约每 20ms 更新。完成后保留最后位置,不是声学测量 |
format |
输出 PCM 格式 {codec="pcm";sampleRate;channels;bitsPerSample=16} |
error |
播放失败信息 |
callbackError |
用户事件函数抛出的错误,已捕获,不向定时器外抛出 |
cleanupError |
异常驱动清理错误。保留仍被设备引用的资源,可再次调用 close() |
onStart、onEnd、onError 的 owner 都是播放器。onStart 代表首次向声卡提交 PCM,不保证该时刻扬声器已经出声。暂时断供只进入 buffering,不会触发 onEnd;继续 write 可以续播。
sampleRate=24000,channels=1,匹配目前 web.edgeTextToSpeech 的 24kHz / 48kbps / 单声道 MP3。其他来源必须提供实际采样率、声道数;参数不是重采样设置,不探测格式。参数与实际格式不一致时的行为见 mp3Decoder:采样率不一致时 finish() 报告“未解码出音频”,声道数不一致时播出的声音是错的。本机重点验收的是上述 Edge MP3 格式。volume 范围为 0~1,默认 1;软件调整 PCM,不改系统音量,0 可用于静音测试。null,err。finish() 返回“未解码出音频”并触发 onError。不会像 BASS 那样因探测未知数据占用大量进程地址空间。close 时统一释放。maxPcmBytes 默认为 64 MiB,限制累计 PCM,不是 MP3 大小;超限明确失败,不覆盖旧数据、不隐式落盘。24kHz 单声道 PCM 每秒约 48000 字节,64 MiB 约 23 分钟。win.loopMessage();无可见窗口的脚本可用 wait() / thread.delay() 处理消息。不要在本播放器事件函数中调用 wait()。task.cancel() 和 player.close():前者停止合成,后者停止声音。窗口销毁也应执行这两项清理。可以在播放器自己的 onEnd、onError 中执行这两项清理。WMPlayer.OCX 的扩展。既有 wsock.tcp.httpStreamMp3 与各种 URL 消费者不受影响。playedSeconds 正常前进、state 正常结束,但音箱没有声音,最常见的原因是本进程的音频会话被静音或音量为 0(Windows 音量合成器中的应用音量),而不是解码或 waveOut 出错:
WMPlayer.OCX 的 settings.mute / settings.volume、MediaPlayer.MediaPlayer 的 Mute 都会直接改写本会话并被记住,之后同一 EXE 的所有进程(包括 waveOut、BASS 等输出)都受影响。在同一进程里做过"静音测试"后,后续原生播放会全部无声。volume 选项只是软件增益,不读写会话。用 sys.audioVolume.session() 读取本进程的会话状态,返回 { volume=0..1; mute=布尔; audible=未静音且音量大于 0 };sys.audioVolume.session(volume,mute) 设置后返回同样的表,任一参数为 null 表示不修改:
import sys.audioVolume.session;
var session = sys.audioVolume.session();
if(session && !session.audible){
// 用户确认后再恢复;这会修改 Windows 记住的应用音量
sys.audioVolume.session(1,false);
}
sys.audioVolume.session 操作的是本进程的应用音量(0~1);sys.audioVolume() 创建的对象操作的是系统主音量(0~100),两者不同。也可以直接在系统托盘音量图标 » 打开音量合成器中把本程序的音量恢复为非静音。