aardio 文档

Edge 流式文本转语音

web.edgeTextToSpeech 是一个只负责合成的 TTS 适配器。它提交一次完整文本,按顺序交付服务端返回的 MP3 字节,不负责播放、存文件或管理全局文本队列。

快速开始:收到音频就播放

import web.edgeTextToSpeech;
import sys.mediaFoundation.streamingMp3Player;

var player,err = sys.mediaFoundation.streamingMp3Player();
if(!player) return print(err);

var engine = web.edgeTextToSpeech({voice="zh-CN-XiaoxiaoNeural";rate=1;});
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();

播放器直接作为第二个参数:收到的音频交给 player.write,合成成功后调用 player.finish(),失败时调用 player.finish(错误信息)。finish() 只表示没有更多输入,剩余声音继续播放;合成结束与播放结束是两件不同的事。

sys.mediaFoundation.streamingMp3Player 是 Windows 自带解码器实现的原生播放器,免费、不需要附带 DLL。

GUI 中通常不用 wait():保留 task 与 player,写一个 stop() 依次调用 task.cancel()、player.close(),停止按钮、窗口关闭、播放结束和出错都调用它。取消、关闭之后不会再有任何回调,不需要额外的状态变量。完整示例见 ~/examples/Media/Audio/edgeTTS-mediaFoundation.aardio。

输出对象

synthesize 的第二个参数可以是任何带 write、finish 方法的对象:

情况 引擎的调用
收到音频 output.write(data)。返回 false,err 或 null,err 表示拒绝,合成以 consumer_error 终止
合成成功 output.finish()
合成失败 output.finish(err.message)
调用 task.cancel() 不调用 output,由调用者自己 close
参数错误 synthesize 返回 null,err,不调用 output

可以直接传入:

可替换的最小契约

输出对象是回调表的便捷写法。业务代码最少只依赖:

engine.synthesize(request, handlers) -> task, err
handlers.onAudio(data, format)
handlers.onBoundary(info)           // 可选
handlers.onFinish(result, err)
task.cancel()
task.wait(timeoutMs)                // 可选等待封装
engine.voices(locale)               // 可选:列出音色,每项至少有 id、locale、gender

传入输出对象,等价于传入下面的回调表。只支持回调表的其他引擎,用同样几行代码就能接收输出对象:

var toHandlers = function(output){
    return {
        onAudio = function(data){ return output.write(data); };
        onFinish = function(result,err){
            if(!err) output.finish();
            elseif(err.code!=="cancelled") output.finish(err.message);
        };
    };
}

替换供应商时,调整引擎创建代码、音色配置及需要使用的能力。保存和播放代码不需要知道 WebSocket、SSML、鉴权参数或 .NET 类型。

创建引擎与请求参数

构造器 web.edgeTextToSpeech(config) 不联网。config 提供默认值,synthesize 的请求字段覆盖这些默认值。

字段 含义
text 必填,UTF-8 文本,非 SSML
voice 音色 ID,默认 zh-CN-XiaoxiaoNeural,由具体供应商定义
rate 速度倍率,默认 1,允许 0.1~3
volume 合成音量倍率,默认 1,允许 0~2;0 不会被默认值覆盖
format 此适配器仅支持 "mp3"
boundary "sentence"(默认)、"word"、"none"
connectTimeoutMs 连接时限,默认 10000 毫秒
receiveTimeoutMs 收到响应之间的空闲时限,默认 30000 毫秒
timeoutMs 整个任务时限,默认 120000 毫秒
providerOptions Edge 专用表,可设 pitch="+0Hz"、language="en-US"

速度与音量会转换为整数百分比。providerOptions 是供应商相关参数,不应作为跨引擎能力假定。当前传输未提供 HTTP 代理接口,传入 proxy 会明确失败。

本适配器限制清理并 XML 转义后的文本不超过 4096 个 UTF-8 字节。这是保守的客户端策略,不是对服务端硬限制的声明。不会自动切片、改写句子、去重或将多个独立 MP3 混成一个请求。

选择音色

voice 决定是谁在说话:男声女声、播音腔还是活泼、普通话还是粤语或方言。engine.voices(locale) 联网取得服务端当前可用的音色(进程内缓存,第二次不联网;请求在工作线程执行,GUI 线程调用也不会卡住界面):

import web.edgeTextToSpeech;
var engine = web.edgeTextToSpeech();
var list,err = engine.voices("zh"); //按地区前缀筛选,忽略大小写;省略则返回全部(约 300 个)
for(i,v in list) print(v.id,v.gender,v.locale,v.localeName,string.join(v.personalities,"/"));

每项是 {id;locale;localeName;gender;name;categories;personalities},id 就是 synthesize 的 voice,gender 为小写的 "female"/"male"。写错音色 ID 时合成以 Unsupported voice 失败,可以用列表核对。

2026 年实测可用的中文音色(标签来自服务端):

voice 性别 口音 风格标签
zh-CN-XiaoxiaoNeural 女 普通话 温暖(新闻、小说)
zh-CN-XiaoyiNeural 女 普通话 活泼(卡通、小说)
zh-CN-YunxiNeural 男 普通话 活泼、阳光
zh-CN-YunjianNeural 男 普通话 激情(体育)
zh-CN-YunyangNeural 男 普通话 专业、可靠(新闻播音)
zh-CN-YunxiaNeural 男 普通话 可爱(童声)
zh-CN-liaoning-XiaobeiNeural 女 东北话 幽默
zh-CN-shaanxi-XiaoniNeural 女 陕西话 明亮
zh-HK-HiuGaaiNeural、zh-HK-HiuMaanNeural 女 粤语 友好
zh-HK-WanLungNeural 男 粤语 友好
zh-TW-HsiaoChenNeural、zh-TW-HsiaoYuNeural 女 台湾普通话 友好
zh-TW-YunJheNeural 男 台湾普通话 友好

同一段文字换音色时长会不同(实测同一句 5.1~6.5 秒,粤语最长),需要对齐画面时以实际音频为准;各音色的原始响度也相差约 5dB,混音时宜逐句归一化。

音频回调

onAudio(data,format) 按收到的顺序调用:

核心不会积累整段音频。慢消费者应使用有界缓冲,并在超限时明确失败(例如原生播放器的 maxPcmBytes)。

边界回调

onBoundary(info) 的 info 包含:

一个服务器元数据消息里的多项边界会全部按序交付。

终结、取消与错误

只保存音频,不加载播放器

import web.edgeTextToSpeech;

var path = io.tmpname(,".mp3");
var file,err = io.file(path,"w+b");
if(!file){print(err);return;}
var task,err = web.edgeTextToSpeech().synthesize({text="保存音频,不绑定播放器。";}, {
    onAudio = function(data){return file.write(data);};
    onFinish = function(result,err){file.close();};
});
if(!task){file.close();io.remove(path);print(err.message);return;}
var result,err = task.wait();
if(result) print(path);
else {io.remove(path);print(err.message);}

文件由调用者创建和关闭。正式程序若要替换已有文件,应先写临时文件,合成成功后再提交,失败时保留已有文件。

示例

网页示例使用 wb.invoke 异步转交音频,串行处理 SourceBuffer.appendBuffer,所有块进入缓冲后才 endOfStream()。播放器自行探测 audio/mpeg 支持,失败时不会假装成功。JS 反向取消 aardio 任务时使用异步调用,避免双向同步等待。

这里不假定传统 web.form 和 WebView2 具有相同的媒体能力。

按需分句

import string.sentences;
var sentences = string.sentences("第一句。第二句。最后一句。");
for i,sentence in sentences {
    print(i,sentence);
}

string.sentences 不需要 LLM。只有文本确实需要分段时才使用它;已经完整且符合长度策略的文本,优先一次提交并流式消费音频。

如果后续文本尚未产生,可以在上层对每句创建独立任务、有限预取并按序播放。每段使用独立播放器/流生命周期,不把独立 MP3 文件当作同一编码流随意拼接,也不要用文本内容作为队列的唯一键(重复句子是合法输入)。本库不再提供隐式全局队列。

迁移与维护

旧的 voiceOptions、ConnectAsync、SetProxy、OnResponseEnd、pushText/popSpeech 已移除,不提供兼容层。

Markdown 格式