使用百度智能云AI语音合成API和pcm-player组件,开发移动端H5文章AI语音实时朗读功能
本文介绍了基于百度智能云API实现H5文章语音朗读功能的开发方案。针对长文本即时播放需求,通过对比三种语音合成API后选择流式文本在线合成方案。重点解决了移动端自动播放限制问题,采用pcm-player组件实现音频流连续播放。文章详细说明了技术实现步骤,包括创建pcm-player实例、WebSocket连接配置、音频流处理等关键代码,并指出了参数设置、状态管理等注意事项。该方案有效克服了移动端安
项目需求: 开发手机H5前端,页面有一篇文章,点击朗读按钮,开始播放AI语音朗读文章内容。客户已选择使用百度智能云产品。
关键技术栈: Vue3 + websocket + pcm-player
在百度智能云文档中心-语音合成栏目中,百度智能云API支持短文本在线合成、长文本在线合成与流式文本在线合成。
实现方案:
- 因为文章字数较多,首先pass短文本在线合成API。
- 从文档描述来看,需求更符合长文本在线合成API,但实际开发后发现,长文本在线合成的返回速度实在是太慢,创建一次任务要等1分钟左右才能返回音频结果,不符合即时播放的需求,该方案也pass。
- 使用流式文本在线合成API,因为要求点下按钮即时播放,所以不能等音频流全部传完合成后再播放,这样也会有等待时间。采用将返回的音频流存储在播放队列中,按次序循环播放的方法。
问题: 第三种方案是靠谱的,但这种方法有一个坑,开发到最后,PC端测试没有问题,但是在移动端访问会出现播放报错,输出内容为:Uncaught (in promise) DOMException: play() can only be initiated by a user gesture.
测试手机为安卓机。
因为手机的浏览器有一种安全措施:禁止自动播放音视频。只有用户手动触发点击才能够播放,触发执行的代码结束后,后续代码调用的播放全部无效。因此函数循环递归触发连续序列播放的方法在手机上行不通。
那么只好修改一下方案3了,既然不能循环调用,那么有没有方法不用递归,自动就能按顺序播放呢?经过搜索,发现了pcm-player这个组件可以满足需求。它不像原生的audio每个实例只能播放一个文件生成的url,而是使用feed方法直接将音频流一个一个放进去,自动按顺序播放。
代码:
1.创建pcm-player实例
var player = new pcmPlayer({
inputCodec: 'Int16', // 采样位数
sampleRate: 16000,
channels: 1, // 通道
onstatechange: (node, event, type) => {}, // 播放状态变化事件
onended: (node, event) => {}, // 播放结束事件
});
player.volume(1);
注意:
- sampleRate和channels这两个参数乱改会出现音频音调改变的问题, sampleRate为16000、channels为1或者sampleRate为8000、channels为2时是正常的,具体看实际情况。
- volume设置音量,虽然文档写的 0 to +∞ ,但可设置范围实际为0到1,超过会出现噪音,而且默认值也很小明显不是1。这里把volume设置成1最为合适。
- onended方法每个音频流播完都会调用,并不是所有音频流播完时才调用。
2.websokect请求
export const baiduStreamSpeechSynthesis = async (text: Array<string>) => {
// text为分段传输文本数组
// 切换播放状态,因为这里需求只有一个按钮控制播放暂停,此处逻辑根据具体需求做调整
playStatus = !playStatus;
if (player.audioCtx.state === 'running') {
// 停止播放
stopAudio();
return;
}
// 正式代码
try {
// 1. 获取 Access Token
const accessToken = await getAccessToken();
// 2. 建立 WebSocket 连接
ws = new WebSocket(BAIDU_SPEECH_WS_URL + `?access_token=${accessToken}&per=${0}`);
ws.binaryType = 'arraybuffer';
// 3. websocket 连接成功
ws.onopen = () => {
console.log('百度语音流式合成连接成功,开始发送鉴权和文本数据');
// 初始化
const initData = {
type: 'system.start',
payload: {
spd: 5,
pit: 5,
vol: 5,
// audio_ctrl: '{"sampling_rate":16000}',
aue: 4,
},
};
ws.send(JSON.stringify(initData));
};
// 4. 接收 websocket 消息
ws.onmessage = (event: MessageEvent) => {
if (typeof event.data == 'string') {
// 接收状态消息
try {
const res = JSON.parse(event.data);
if (res.message === 'success' && res.type === 'system.started') {
// 初始化成功,开始传输文本
text.forEach((text: string) => {
wsSendText(text);
});
// 传输完毕
const finishData = {
type: 'system.finish',
};
ws.send(JSON.stringify(finishData));
} else if (res.message === 'success' && res.type === 'system.finished') {
// 文本传输完毕时,关闭
ws.close();
ws = null;
}
} catch (error) {
const err = error as Error;
console.error('解析新版接口消息异常:', err);
}
} else {
// 接收二进制音频流
player.feed(event.data);
// 如果未播放,则触发首次播放
if (player.audioCtx.state === 'suspended') {
player.continue();
}
}
};
// 5. websocket 错误回调
ws.onerror = (error: Event) => {
const errorMsg = '语音流式合成连接/传输异常';
const err = new Error(`${errorMsg}:${(error as ErrorEvent).message || '未知错误'}`);
console.error(err, error);
};
// 返回 websocket 实例(便于外部手动关闭连接)
return ws;
} catch (error) {
const err = error as Error;
throw err;
}
};
重点:
- getAccessToken()通过key获取用户token,参考百度智能云文档鉴权认证栏编写代码。
- 由于pcm-player.feed()只接收ArrayBuffer类型或者TypedArray 类型,创建websocket连接时用ws.binaryType = 'arraybuffer’将接收数据流设置为ArrayBuffer类型更方便。
- 发送system.start时记得将传输参数payload.aue音频格式设置为4,否则合成的语音只有杂音。(3=mp3-16k/24k,4=pcm-16k/24k,5=pcm-8k,6=wav-16k/24k,默认为3)
- 发送合成文本可以循环调用发送,所有文本发送完毕后可以立即发送system.finish,接收的数据流是按顺序返回的,不会干扰合成进程。
- pcm-player属性audioCtx.state可以判断播放器状态,running为正在播放,suspended为暂停。全部数据流播放完毕后并不会将状态切为suspended,依旧为running,只有调用player.pause()才会将状态切换为suspended。
代码仅供参考,具体逻辑还需根据实际业务需求做调整。
更多推荐

所有评论(0)