WebAudio
软电话专利:
- 一种基于数据分片的实时语音流传输和缓冲方案
- SocketIO模拟电话通信的网络对讲机工具
方案一 audio标签 + mediaRecorder()
思路:
发送端
navigator.mediaDevices.getUserMedia()
实现录音,将获取的stream音频流用于初始化MediaRecoder
,并通过ondataavailable()
事件实现获取一段段数据,存于队列中,将队列通过webSocket发送给后端
接收端
从后端处接收到数据后,设置mimeType转blob,再将blob通过window.URL.createObjectURL(blob)
生成临时URL动态赋值给audio标签的src实现播放
缺陷
- 没办法通过mimeType转成中台需要的PCM流
方案二 audio标签 + 第三方库(js-audio-recorder)
第三方库文档:http://recorder.api.zhuyuntao.cn/
思路:
发送端
第三方库实现录音,录音后的PCM流转ArrayBuffer通过WebSocket发送给后端
接收端
从后端处接收到数据后加Wav文件头后转blob通过window.URL.createObjectURL(blob)
创建临时url动态传输给audio标签的src实现播放
实现步骤
- 直接
new Recoder()
获取录音实例 - 通过第三方库的
getPermission()
获取录音权限 - 通过第三方库的
start()
和getPCMBlob()
实现录音 - socket.send(pcm)
- 接收到数据后(ArrayBuffer格式),加44位Wav头信息,转成blob格式
window.URL.createObjectURL(blob)
后生成临时URL- 动态赋值给audio标签的src实现播放
缺陷
- iphone无法兼容audio的autoplay问题
- 整段传输,耗时长
方案三 AudioContext无标签实现
思路:
共同:在需要播放的场景前创建白噪音,引诱用户产生交互播放白噪音,通过iphone的安全策略
发送端
创建audioContext实例,用window.navigator.mediaDevices.getUserMedia()
获取音频流,并利用录音流创建音频流节点,ScriptProcessorNode
等各环节操作节点,在按下后,通过句柄引流录音流去执行对应节点,在ScriptProcessorNode
中进行分片,压缩,转码,加文件头操作,并回调到前端对后端进行发送,松开后断开引流,
接收端
获取数据后进行解码,转码后连接至audioContext实例实现播放
实现步骤
创建并维护
audioContext
单例在需要实现自动播放的场景前,通过
audioContext
实例的createBuffer
方法创建白噪音并引诱用户交互,播放白噪音从而解决iphone的安全策略问题window.navigator.mediaDevices.getUserMedia()
方法获取音频流,并在初始化的阶段创建录音源节点MediaStreamAudioSourceNode
和script处理器节点ScriptProcessorNode
录音与否其实只是音频流的处理与否
- 按下录音按钮时:
- 将录音源节点connect至script处理器节点
- 创建分片的回调
onaudioprocess
(在创建script处理器节点时可设置,满多少bit为一个分片)- 通过
getChannelData
获取分片中每一声道的数据(Float32Array) - 由于不同设备的采样率不一样,需要按照需要的采样率进行压缩
- 将压缩好的
Float32Array
进行处理变成PCM流(arrayBuffer) - 作为参数调用回调,将pcm传至前端通过socket发送给后端
- 通过
- script处理器节点connect至audioContext实例
- 松开录音按钮:
- script处理器节点disconnectaudioContext实例
- 录音源节点disconnectscript处理器节点
- 重新初始化参数
- 按下录音按钮时:
接受到后端转发的数据(arrayBuffer)后,
decodeAudioData
实现转码(audioBuffer)通过
createBufferSource
创建缓冲区,将audioBuffer填充至缓冲区将缓冲区链接audioContext实例实现播放
方案四 WebRTC
未实现,待补充
零碎知识
Wav的44位文件头
PCM(Pulse-code modulation | 脉冲编码调制)
pcm就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程。
PCM三要素
- 声道(能支持不同发生的音响的个数)(channel)
- 单声道:Mono,单喇叭或者两个喇叭输出同一个声道的声音(在线 ASR 录音用的是单声道)
- 双声道:Stereo,分左声道和右声道,更加有空间效果
- 采样率(sampleRate)
- 每秒对声音进行采集的次数,同样也是所得的数字信号的每秒样本数,单位为 Hz。
- 采样位数(bitDepth)
- 采样后,将采样的单个样本进行量化,用来衡量声音波动变化的一个参数,也可以说是声卡的分辨率。
- n-bit 指的是声音的强度(振幅)被均分为 2^n 级,常用的有 8bit(1字节)、16bit(2字节)、32bit(4字节)
ArrayBuffer
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区(预分配内存)。
单位是字节,它是一个字节数组,通常在其他语言中称为 byte array
不能直接操作 ArrayBuffer 的内容,只能通过 DataView 或者定型数组对象操作
ArrayBuffer 分配的内存不能超过 Number.MAX_SAFE_INTEGER(2^53 - 1) 字节
AudioBuffer
- AudioBuffer接口表示存在内存里的一段短小的音频资源。
- 这些类型对象被设计来控制小音频片段,往往短于45秒
- 缓存区(buffer)包含以下数据:不间断的IEEE75432位线性PCM,从-1到1的范围额定
- 如果AudioBuffer有不同的频道,他们通常被保存在独立的缓存区
字节序
字节序,或字节顺序(”Endian”、”endianness” 或 “byte-order”),描述了计算机如何组织字节,组成对应的数字。
每一个字节可以存储一个 8 位(bit)的数字(0x00-0xff),存储更大数字需要多个字节,现在大部分需占用多字节的数字排列方式是 little-endian(低位字节排放在内存中低地址端,高字节排放在内存的高地址端),与 big-endian 相反。
举例:用不同字节序存储数字 0x12345678
(即十进制中的 305 419 896)
little-endian:
0x78 0x56 0x34 0x12
big-endian:
0x12 0x34 0x56 0x78