使用集成接入文字转语音
连接 Azure 语音集成后在 Func 里用 ctx.tts.azure.speak 把文字变成语音:可调音色、语速、音高与说话风格,upload 直接存进项目空间返回永久地址。订阅密钥只保存在服务端,文本由平台转义后拼成 SSML。
连接 Azure 语音集成之后,Func 里用 ctx.tts.azure.speak() 把文字变成语音:朗读文章、生成播客、做有声书、给页面加语音提示。订阅密钥只保存在服务端,代码里不会出现。
连接 Azure 语音
先在 Azure 门户创建一个语音服务资源:
- 打开 创建语音服务,选订阅、资源组和区域。免费层 F0 就够试用。
- 创建完成后进「密钥和终结点」,复制密钥 1。
- 记下位置/区域,形如
eastus、eastasia、southeastasia。
然后打开编辑器的后端 → 集成,点 Azure 语音:
| 字段 | 填什么 | 必填 |
|---|---|---|
| 密钥 | 「密钥和终结点」页面里的密钥 1 | 是 |
| 区域 | 短区域名,如 eastus。不要填整条地址 | 是 |
| 默认音色 | 调用不指定时用它,默认 zh-CN-XiaoxiaoNeural | 否 |
| 音频格式 | 留空就是 mp3 | 否 |
| 渠道标签 | 同一项目可以接多份配置(比如中文一份英文一份),用标签区分 | 否 |
保存时平台会真的向 Azure 请求一次音色列表,密钥或区域不对当场报错。
speak:合成一段语音
最常见的写法是只传文字:
export function read(input, ctx) {
const audio = ctx.tts.azure.speak(input.text)
return { audioBase64: audio.audioBase64, mimeType: audio.mimeType }
}
要调音色、语速、情绪,传一个对象:
export function read(input, ctx) {
const audio = ctx.tts.azure.speak({
text: input.text,
voice: 'zh-CN-YunxiNeural',
rate: '+10%',
pitch: '-2st',
style: 'cheerful',
styleDegree: 1.5,
format: 'audio-24khz-48kbitrate-mono-mp3',
upload: true,
})
return { url: audio.fileUrl }
}
| 参数 | 说明 |
|---|---|
text | 必填。纯文本,不是 SSML,见为什么不收 SSML |
voice | 音色名,留空取集成里配的默认值 |
rate | 语速。+20%、-10%、1.2,或 slow / fast 这类词 |
pitch | 音高。+10%、-2st(半音)、high / low |
style | 说话风格,如 cheerful、sad、newscast-casual。音色要支持才有效 |
styleDegree | 风格强度,常用 0.01 到 2 |
language | 语言标签,如 zh-CN。一般不用填,音色自带 |
format | 输出格式,留空是 mp3,见音频格式 |
upload | true 时直接存进项目空间并返回 fileUrl |
filename | 只在 upload 时有用。默认按内容哈希命名 |
返回:
| 字段 | 说明 |
|---|---|
fileUrl | 只有 upload: true 时有。可以直接放进 <audio src> |
audioBase64 | 不传 upload 时有。音频原文的 base64 |
mimeType | 如 audio/mpeg |
bytes | 音频字节数 |
voice | 实际用到的音色 |
format | 实际用到的格式 |
upload: true 通常是你要的那条路
不传 upload 时音频以 base64 回到你的代码里。一分钟的语音大约是 500KB,base64 之后接近 700KB 的字符串,要占 Func 的内存预算,再随返回值传给前端又要占一次带宽。
带上 upload: true,音频在服务端直接进项目自己的文件空间,你只拿到一个永久地址:
const audio = ctx.tts.azure.speak({ text: paragraph, upload: true })
ctx.db.insert('chapters', { text: paragraph, audioUrl: audio.fileUrl })
什么时候不用 upload:一次性的短提示音、马上就播不需要保存的内容。
为什么只收纯文本,不收 SSML
Azure 接口本身吃的是 SSML,平台会替你拼,并把你的文字转义之后放进去。你不能自己写 SSML 标签。
确实需要整段自己写 SSML 的话,用 ctx.ai.openai.call() 那样的思路:这个能力目前不开 SSML 直通,需要的话可以在 Func 里自己配环境变量走 fetch。
音频格式
留空就是 mp3,绝大多数场景都够用。常用取值:
| 格式 | 用途 |
|---|---|
audio-24khz-48kbitrate-mono-mp3 | 默认。体积小,网页播放够用 |
audio-48khz-192kbitrate-mono-mp3 | 音质更好,文件更大,适合有声书 |
riff-24khz-16bit-mono-pcm | WAV,适合再做后期处理 |
ogg-24khz-16bit-mono-opus | Opus,同码率下音质更好,浏览器支持要自己确认 |
常用音色
| 音色 | 说明 |
|---|---|
zh-CN-XiaoxiaoNeural | 中文女声,默认。支持多种 style |
zh-CN-YunxiNeural | 中文男声,偏年轻 |
zh-CN-YunjianNeural | 中文男声,适合解说与体育播报 |
zh-CN-XiaoyiNeural | 中文女声,情感表现更强 |
en-US-JennyNeural | 英文女声 |
en-US-GuyNeural | 英文男声 |
完整列表在 Azure 语音的语言与音色支持,注意不是每个音色都支持 style。
多渠道:via(tag)
一个项目可以接多份配置,比如中文一份、英文一份,或者试用层和付费层各一份。给每份写上渠道标签,代码里用 via() 选:
ctx.tts.azure.via('zh').speak({ text: '你好' })
ctx.tts.azure.via('en').speak({ text: 'hello' })
不写 via() 时用标签为 default 的那份。
缓存与去重要自己做
平台不缓存:同一句话调两次就是两次钱。要省钱自己建一张表存「文本哈希 → 地址」。
export function speakCached(input, ctx) {
const key = input.text.trim()
const hit = ctx.db.query('tts_cache', { where: { text: key }, limit: 1 })
if (hit.list.length > 0) return { url: hit.list[0].url }
const audio = ctx.tts.azure.speak({ text: key, upload: true })
ctx.db.insert('tts_cache', { text: key, url: audio.fileUrl })
return { url: audio.fileUrl }
}
不内置是因为「什么算同一句」只有你知道:换了音色算不算?语速呢?把这个判断做进平台,只会做出一个你不同意的版本。
报错怎么看
| 报错 | 该改哪一项 |
|---|---|
tts integration has no api key | 集成没连,或密钥被清空了 |
azure_tts_region is required | 区域没填 |
invalid azure_tts_region | 区域填成了整条地址或带空格的显示名,要 eastus 这种短名 |
text is required | 传进来的文字是空的 |
invalid rate / invalid pitch / invalid style | 值没通过白名单。参考上面各参数的取值说明 |
invalid voice | 音色名格式不对,形如 zh-CN-XiaoxiaoNeural |
Azure 自己的报错(配额用尽、音色不存在、区域与密钥不匹配)会带上文案转成 400 或 502,不会把上游的 401 直接抛给前端。
不做的事
| 不做 | 为什么 |
|---|---|
| SSML 直通 | 文本常常来自用户输入,不转义就是注入。见上文 |
| 缓存与去重 | 「什么算同一句」只有站点知道 |
| 长文本自动分段 | 怎么断句、要不要按章节分文件,是内容的事不是接口的事 |
| 其他 TTS 服务商 | 目前只有 Azure。真有人要再按同一套接口加 |
相关文档:更多集成、OpenAI 模型集成、Func 后端能力。
