aardio 文档

sys.mediaFoundation.streamingMp3Player

Windows 原生流式 MP3 播放器,边收边播:MP3 字节 → sys.mediaFoundation.mp3Decoder → 16 位 PCM → sys.waveOut。

音频数据在内存中处理,不写临时文件,不依赖 BASS、浏览器、OCX 或音频 HTTP 服务。可以直接接收 TTS 的音频,也可以接收应用从网络取得的 MP3 字节。需要自己处理 PCM 时,直接用它的两个组成模块。

最小 Edge TTS 示例

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 可以续播。

格式与生命周期

播放无声排查

playedSeconds 正常前进、state 正常结束,但音箱没有声音,最常见的原因是本进程的音频会话被静音或音量为 0(Windows 音量合成器中的应用音量),而不是解码或 waveOut 出错:

用 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),两者不同。也可以直接在系统托盘音量图标 » 打开音量合成器中把本程序的音量恢复为非静音。

Markdown 格式