乐趣区

HTML5实时语音通话聊天MP3压缩传输3KB每秒

自从 Recorder H5 GitHub 开源库优化后,对边录边转码成小语音片段文件实时上传服务器这种操作支持非常良好,因此以前不太好支持的 H5 语音通话已经有了更好的突破空间。因此花了两晚时间打造了一个 H5 语音通话聊天的 demo。

欢迎在线把玩:https://xiangyuecn.github.io/Recorder/

一、把玩方法

  1. 准备局域网内两台设备 (Peer A、Peer B) 用最新版本浏览器 (demo 未适配低版本) 分别打开 demo 页面(也可以是同一浏览器打开两个标签)
  2. 勾选页面中的 H5 版语音通话聊天,在 Peer A 中点击新建连接
  3. 把 Peer A 的本机信手动复制传输给 Peer B,粘贴到远程信息中,并点击确定连接
  4. 把 Peer B 自动生成的本机信息手动复制传输给 Peer A,粘贴到远程信息中,并点击确定连接
  5. 双方 P2P 连接已建立,使用页面上方的录音功能,随时开启录音,音频数据会实时发送给对方

局域网 H5 版对讲机????

二、技术特性

(1)数据传输

github demo 中考虑到减少对服务器的依赖,因此采用了 WebRTC P2P 传输功能,无需任何服务器支持即可实现局域网内的两个设备之间互相连接,连接代码也算简单。有服务器支持可能就要逆天了,不过代码也会更复杂。

如果正式使用,可能不太会考虑使用 WebRTC,用 WebSocket 通过服务器进行转发可能是最佳的选择。

WebRTC 局域网 P2P 连接要点(实际代码其实差不多,只不过多做了点兼容):

/******Peer A(本机)******/
var peerA=new RTCPeerConnection(null,null)

// 开启会话,等待远程连接
peerA.createOffer().then(function(offer){peerA.setLocalDescription(offer);
    peerAOffer=offer;
});

var peerAICEList=[......] // 通过 peerA.onicecandidate 监听获得所有的 ICE 连接信息候选项,如果有多个网络适配器,就会有多个候选

// 创建连接通道对象,A 端通过这个来进行数据发送
var peerAChannel=peerA.createDataChannel("RTC Test");



/******Peer B(远程)******/
var peerB=new RTCPeerConnection(null,null)

// 连接到 Peer A
peerB.setRemoteDescription(peerAOffer);

// 开启应答会话,等待 Peer A 确认连接
peerB.createAnswer().then(function(answer){peerB.setLocalDescription(answer);
    peerBAnswer=answer;
});

// 把 Peer A 的连接点都添加进去
peerB.addIceCandidate(......peerAICEList)

var peerBICEList=[......] // 通过 peerB.onicecandidate 监听获得所有的 ICE 连接信息候选项,如果有多个网络适配器,就会有多个候选

var peerBChannel=... // 通过 peerB.ondatachannel 得到连接通道对象,B 端通过这个来进行数据发送


/******* 最终完成连接 ********/
// 连接到 Peer B
peerA.setRemoteDescription(peerBAnswer);

// 把 Peer B 的连接点都添加进去
peerA.addIceCandidate(......peerBICEList)

/*
peerA peerB 分别等待 peerA/BChannel.onopen 回调即完成 P2P 连接,然后通过监听 peerA/BChannel.onmessage 获得对方发送的信息,通过 peerA/BChannel.send(data) 发送数据。*/

(2)音频采集和编码

由于是在我的 Recorder 库中新加的 demo,因此音频采集和编码都是现成的,Recorder 库有好的兼容性和稳定性,因此节省了最大头的工作量。

编码最佳使用 MP3 格式,因为此格式已优化了实时编码性能,可做到边录边转码,16kbps 16khz 的情况下可做到 2kb 每秒的文件大小,音质还可以,实时传输时为 3kb 每秒,15 分钟大概 3M 的流量。

用 wav 格式也可以,不过此格式编码出来的数据量太大,16 位 16khz 接近 50kb 每秒的实时传输数据,15 分钟要 37M 多流量。其他格式由于暂未对实时编码进行优化,使用中会导致明显卡顿。

降噪、静音检测等高级功能是没有的,毕竟是非专业人员???? 要求高点可以,但不要超出范围太多啦。

(3)音频实时接收和播放

接收到一个音频片段后,本应该是立即播放的,但由于编码、网络传输导致的延迟,可能上个片段还未播放完(甚至未开始播放),因此需要缓冲处理。

因为存在缓冲,就需要进行实时同步处理,如果缓冲内积压了过多的音频片段,会导致语音播放滞后太多,因此需要适当进行对数据进行丢弃,实测发现网络正常、设备性能靠谱的情况下基本没有丢弃的数据。

然后就是播放了,本应是播完一个就播下一个,测试发现这是不靠谱的。因为结束一个片段后再开始播放下一个发出声音,这个过程会中断比较长时间,明显感觉得出来中间存在短暂停顿。因此必须在片段未播完时准备好下一个片段的播放,并且提前开始播放,达到抹掉中间的停顿。

我写了两个播放方式:

  1. 实时解码播放
  2. 双 Audio 轮换播放

最开始用一个 Audio 停顿感太明显,因此用两个 Audio 轮换抹掉中间的停顿,但发现不同格式 Auido 播放差异巨大,播放 wav 非常流畅,但播放 mp3 还是存在停顿(后面用解码的发现是得到的 PCM 时长变长了,导致事件触发会出现误差,为什么会变长?怪异)。

因此后面写了一个解码然后再播放,mp3 这次终于能正常连续播放了,wav 格式和双 Audio 的播放差异不大。实时解码里面也用到了双 Audio 中的技巧,其实也是用到了两个 BufferSource 进行类似的轮换操作,以抹掉两个片段间的停顿。

不过最终播放效果还是不够好,音质变差了点,并且多了点噪音。如果有现成的播放代码拿过来用就就好了。

三、应用场景

  1. 数据传输改成 WebSocket,做个仿微信语音通话 H5 版还是可以的(受限于 Recorder 浏览器支持)
  2. 局域网 H5 版对讲机(前端玩具)
  3. …… 没有想到

完。

退出移动版