项目需求: 开发手机H5前端,页面有一篇文章,点击朗读按钮,开始播放AI语音朗读文章内容。客户已选择使用百度智能云产品。
关键技术栈: Vue3 + websocket + pcm-player

在百度智能云文档中心-语音合成栏目中,百度智能云API支持短文本在线合成长文本在线合成流式文本在线合成

实现方案:

  1. 因为文章字数较多,首先pass短文本在线合成API。
  2. 从文档描述来看,需求更符合长文本在线合成API,但实际开发后发现,长文本在线合成的返回速度实在是太慢,创建一次任务要等1分钟左右才能返回音频结果,不符合即时播放的需求,该方案也pass。
  3. 使用流式文本在线合成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。

代码仅供参考,具体逻辑还需根据实际业务需求做调整。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐