基于WebRTC的音视频对讲

WebRTC是什么这里不做过多介绍, 可以参考WebRTC官网,以及它的Step by Step教程, 本文描述如何基于该项技术做一个简单的音视频对讲。

环境准备

  • 浏览器:较新版本的Chrome
  • 支持HTTPS的网页服务器,建议使用Nginx反向代理

通讯机制

浏览器使用一个简单的服务端key-value接口来交互数据

key-value实现

使用springboot实现一个简单的kv存储即可

@RestController
@RequestMapping(value = "/kv")
public class KeyValueController {
ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();

@GetMapping(value = "/{key}")
String get(@PathVariable String key){
return map.get(key);
}
@RequestMapping(value = "/{key}", method = RequestMethod.POST)
public String put(@PathVariable String key,
@RequestParam(value = "value") String value) throws UnsupportedEncodingException {
System.out.println(key + ":" + URLDecoder.decode(value,"utf-8"));
map.put(key,value);
return value;
}

}

浏览器调用key-value接口

浏览器使用js来访问kv存储, 除了读写数据外,还加入了一个wait函数,用于等待特定数据就位

//为key添加一个前缀, 可以支持每个房间都有独立的key-value空间
var prefix = config.room;

//设置数据
function set(key, value, result) {
var str = encodeURI(JSON.stringify(value));
$.post("/kv/" + prefix + "_" + key, { value: str }, result);
}

//读取数据
function get(key, result) {
$.get(
"/kv/" + prefix + "_" + key,
null,
r1 => {
result(JSON.parse(decodeURI(r1)));
},
"text"
);
};

//等待指定数据就位
function wait(key,value,callback){
var timer = setInterval(()=>{
get(key,(v)=>{
if(v == value){
clearInterval(timer);
callback();
}
})
},1000);
}

工作流程

为了避免不必要的复杂度, 这里将通讯双方区分为主机和客机, 主机访问host.html,客机访问guest.html

主机工作流程

  • 获取本地音视频,在本地显示
  • 创建RTCPeerConnection,并监听onicecandidate和onaddstream
  • 将本地流加入到RTCPeerConnection中,并createOffer
  • createOffer成功后,设置RTCPeerConnection的本地描述符,并将本地描述符提交给kv存储
  • onicecandidate 监听到candidates列表后,将该信息提交给kv存储,并报告kv存储主机网络已经就绪,并开始等待客户机网络就绪
  • 当确认客户机网络就绪后,从kv存储中去除客户机的描述符和andidates列表,并设置在RTCPeerConnection上
  • onaddstream 监听到事件后,表示客户机的流已经到位, 将客户机的流显示在本地

客机工作流程

  • 获取本地音视频,在本地显示
  • 等待主机的网络就位
  • 当主机网络就位后,创建RTCPeerConnection,并监听onicecandidate和onaddstream
  • 将本地流加入到RTCPeerConnection中
  • 从kv存储中获取主机的candidates和流描述符,设置到RTCPeerConnection中
  • 调用RTCPeerConnection的createAnswer
  • createAnswer成功后,设置RTCPeerConnection的本地描述符,并将本地描述符提交给kv存储
  • onicecandidate监听到candidates列表后,将该信息提交给kv存储,并报告kv存储客机网络已经就绪
  • onaddstream 监听到事件后,表示主机的流已经到位, 将主机的流显示在本地

Turn服务

如果主机和客机处于同一个局域网,那么不需要额外的服务即可通讯, 否则的话,就有可能需要服务器来中转

可以使用Coturn TURN server的docker镜像boldt/coturn来快速搭建turn服务

下面是一段启动脚本

#!/bin/bash
export MIN_PORT=50000
export MAX_PORT=51000
sudo docker run \
-d \
-p 3478:3478 \
-p 3478:3478/udp \
-p ${MIN_PORT}-${MAX_PORT}:${MIN_PORT}-${MAX_PORT}/udp \
-e USERNAME=username \
-e PASSWORD=password \
-e MIN_PORT=${MIN_PORT} \
-e MAX_PORT=${MAX_PORT} \
--restart=always \
--name coturn \
--volume /etc/pki/nginx/cert.pem:/etc/ssl/turn_server_cert.pem \
--volume /etc/pki/nginx/key.pem:/etc/ssl/turn_server_pkey.pem \
coturn

示例代码

https://github.com/xuqifzz/webrtcDemo