前言
前两周做了播放实时语音流的需求,就这一个需求跟后端联调了两天,最后还是拉外援才成功联调。这个需求本质也不是特别难,下面来看一下具体的实现过程
ws获取
标题中的语音流是实时的,公司项目是通过webSocket进行实时语音的获取,如果不是实时语音流,直接跳过。实时听起来很复杂,对于前端而言实现起来却很简单,调用ws几个方法就能实现。
- 创建,new WebSocket实例化一个ws。
- 连接,onopen监听ws连接状态。
- 消息,ommessage监听ws连接的消息,send方法向ws发送消息。
- 错误,onerror监听ws错误消息。
- 关闭,onclose监听ws关闭,close方法关闭ws。
通过ws一系列操作我们就能实时获取后端传过来的数据。
语音流播放
如果直接传语音流前端直接当成流处理会很简单,但后端是通过调用其他平台获取的语音流,传到前端就变成字符串了,于是就是不断尝试的联调过程。
AudioContext
刚开始我的想法是通过AudioContext.createBufferSource()播放,该方法接收AudioBuffer对象,所以我们现在的目的就是将字符串转成AudioBuffer。获取AudioBuffer的方式有两种:
- AudioContext.createBuffer():创建空白的AudioBuffer对象,然后将音频数据填充进去。
- AudioContext.decodeAudioData():将ArrayBuffer(字节数组)数据解码为AduioBuffer。ArrayBuffer一般就是接口返回的文件流。
对比两个方法选用第二个,第二个是需要传过来将字符串变为ArrayBuffer,跟后端沟通了下,反馈这字符串直接就是音频流,所以我就直接当成ArrayBuffer去处理。
传入字符串,运行报错,decodeAudioData解码不了这数据,看了不能直接用。搜索了一番,发现可以用charCodeAt将字符变为unicode编码,就尝试把字符串都变为uniCode再解析,还是不行,继续尝试。
base64编码
尝试了几次,跟后端商议把数据进行base64编码,前端拿到进行解码再进行处理。首先使用window.atob进行解码,然后用new ArrayBuffer创建为字节数组。
再次运行还是不行,因为是pcm流所以另一个同事引入了pcm-player依赖尝试进行播放。
pcm-player
pcm-player是一个简单的pcm流播放器,我们安装此依赖,再页面引入然后初始化一个实例。
然后我们将数据进行处理使用feed方法进行播放,注意该插件也是通过AudioContext.createBufferSource实现语音流播放,只是播放没有可视化的东西。实例化时我们要注意两个参数:
- inputCodec:编码格式,表示类型化数组的类型。
- sampleRate:采样率,录音相关属性。
简单来说这两个属性影响着音质。引入了新插件,继续尝试,因为feed只接受类型化数据,所以我们要把解码后的数据变成TypedArray。我们先声明一个类型化数据,然后利用chatCodeAt将字节数据添加进去。注意,声明的TypedArray的类型要跟inputCodec保持一致。
js
base64ToInt16Array(base64String) {
const rawData = window.atob(base64String)
const typedArray = new Int16Array(rawData.length)
for (let i = 0; i < rawData.length; i++) {
typedArray[i] = rawData.charCodeAt(i)
}
return typedArray
}
因为是实时的,所以我们用watch监听ws的消息,将获取的字符串处理成TypedArray,然后用feed方法去播放。运行语音成功播放出来了,但是,但是音质不行。就因为音质问题跟后端对接了两天,改编码格式、采样率就是不能清晰播放出来,最终拉了一个外援,研究了一下午终于搞出来了。简单来说问题就是字节没对上,也就是前端处理方法有问题,上面给Int16Array赋值的时候需要把两个字节合成一个。
到此录音能实时播放,并且播放的很清晰,完美收官。
总结
以上就是pcm清晰播放实时语音流的过程,只能说闻道有先后,术业有专攻 要不是大佬帮着分析字节问题,不知道还要卡多久。