SRS流媒体服务器(3)视频通话环境搭建和源码分析
1. 概述
本文档介绍如何通过SRS 4.0搭建 WebRTC视频通话环境,涵盖环境配置、服务器编译启动、逻辑分析及测试方法。SRS 源码包提供了信令服务器以及显示UI静态web页面分别在3rdparty/signaling 3rdparty/httpx-static。信令服务器是基于go语言编写,所以需要搭建go环境。
2. 环境搭建
2.1 安装go语⾔环境
#下载安装包
cd /usr/local/
wget https://dl.google.com/go/go1.16.5.linux-amd64.tar.gz --no-check-certificate
tar -C /usr/local -xzf go1.16.5.linux-amd64.tar.gz#配置环境变量
vim /etc/profile
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
source /etc/profile
2.2 编译启动 SRS 流媒体服务器
编译可参考SRS流媒体服务器(1)概述和环境搭建-CSDN博客
#vim conf/rtc.conf
rtc_server {enabled on;listen 8000; #改成自己云服务器ipcandidate xxx.81;
}# 启动 SRS
./objs/srs -c conf/rtc.conf
2.3 编译启动信令服务器
cd 3rdparty/signaling
make && ./objs/signaling # 默认端口 1989
2.4 编译启动 Web 服务器(支持 HTTPS/WSS)
#编译
cd 3rdparty/httpx-static
make
#生成 SSL 证书
openssl genrsa -out server.key 2048
openssl req -new -x509 -key server.key -out server.crt -days 3650
#启动
./objs/httpx-static -http 80 -https 443 -ssk server.key -ssc server.crt \--proxy http://127.0.0.1:1989/sig \--proxy http://127.0.0.1:1985/rtc \--proxy http://127.0.0.1:8080/
3. 测试方法
访问以下 URL 进行测试(替换
localhost
为实际 IP):
https://localhost/demos/
https://192.168.3.6/demos/
3.1 排查失败之云服务器端口开放
当你在公网部署且访问一直在转圈失败,连摄像头都无法打开,那可能是端口未开放。这里排查了很久,居然需要打开3306这个端口!以及8000(rtp配置文件端口)
除此之外SRS 流媒体服务器其他端口说明如图:
4. 源码分析
入口代码 one2one.html:297 $("#btn_start").click(startDemo);
SrsRtcSignalingParse:
- 解析出连接信令服务器的地址
- 房间号:可填或随机
- 显示名:可填或随机
- 主机
拉流和推流函数对象主要做:
- 1.确定协议
- 2.addtrack
- 3. offer/answer协商
- 5.RTCPeerConnection
- 6.parseUrl xxx + '/rtc/v1/play/ 如: 'https://117.72.13.81:443/rtc/v1/push or play/'
var startDemo = async function () {sig = new SrsRtcSignalingAsync(); //srs.sig.js:28 创建SrsRtcSignalingAsync,RTC信令sig.onmessage = function (msg) { //onmessage订阅新的信令消息console.log('Notify: ', msg);if (msg.event === 'leave') {$('#player').hide();}if (msg.event === 'publish') {//房间已经存在的参与者,收到publish信令后再去订阅新加入者if (msg.peer && msg.peer.publishing && msg.peer.display !== display) {startPlay(host, room, msg.peer.display);}}};await sig.connect(conf.wsSchema, conf.wsHost, room, display);//连接websock,见下面SrsRtcSignalingAsync代码let r0 = await sig.send({action:'join', room:room, display:display}); //向信令服务器发送join信令,会返回房间列表。console.log('Signaling: join ok', r0);// 对于一对一演示,当房间已满时发出警报并忽略。if (r0.participants.length > 2) {alert('Room is full, already ' + (r0.participants.length - 1) + ' participants');sig.close();return;}// 如果信令正常,开始推流await startPublish(host, room, display); //向srs流媒体服务器开始推流let r1 = await sig.send({action:'publish', room:room, display:display}); //向信令服务器发送publish信令console.log('Signaling: publish ok', r1);// 拉其他人的流r0.participants.forEach(function(participant) {if (participant.display === display || !participant.publishing) return;startPlay(host, room, participant.display);// 对于新进的房间,会拉取房间内另一个人的流});if (r0.participants.length >= 2) {$('.srs_merge').show();}};var startPublish = function (host, room, display) {$(".ff_first").each(function(i,e) {$(e).text(display);});var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query;$('#rtc_media_publisher').show();$('#publisher').show();if (publisher) {publisher.close();}publisher = new SrsRtcPublisherAsync(); //创建RTC异步推流 srs.sdk.js:20 与下文类似$('#rtc_media_publisher').prop('srcObject', publisher.stream);};var startPlay = function (host, room, display) { //向srs服务器拉流$(".ff_second").each(function(i,e) {$(e).text(display);});//拼接url "webrtc://117.72.13.81/63838c2/1907a7c",其中webrtc://是协议头,"117.72.13.81"是srs服务器ip,"63838c2"是房间号,"1907a7c"是参与者昵称var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query;$('#rtc_media_player').show();$('#player').show();if (player) {player.close();}player = new SrsRtcPlayerAsync();//创建RTC异步拉流 srs.sdk.js:278 1.确定协议 2.addtrack 3. offer/answer协商 5.RTCPeerConnection 6.parseUrl xxx + '/rtc/v1/play/ 如: 'https://117.72.13.81:443/rtc/v1/publish/',$('#rtc_media_player').prop('srcObject', player.stream);};// Pass-by to SRS url. 用于解析出连接信令服务器的地址let conf = SrsRtcSignalingParse(window.location);$("#btn_start").click(startDemo);// Never play util windows loaded @see https://github.com/ossrs/srs/issues/2732if (conf.autostart) {window.addEventListener("load", function(){ startDemo(); });}
});
SrsRtcSignalingAsync 信令函数对象主要做:
- connect:连接信令服务器 wss://xxxx/sig/v1/rtc
- onmessage:处理接收到的消息
- send : 把对象序列化后发送到服务器
- close: 关闭
function SrsRtcSignalingAsync() { //信令函数var self = {};// The schema is ws or wss, host is ip or ip:port, display is nickname// of user to join the room.self.connect = async function (schema, host, room, display) {var url = schema + '://' + host + '/sig/v1/rtc'; //如:api:"wss://117.72.13.81/sig/v1/rtc"self.ws = new WebSocket(url + '?room=' + room + '&display=' + display); //与url建立连接 参数:房间号,昵称self.ws.onmessage = function(event) { //收到消息的处理函数var r = JSON.parse(event.data);var promise = self._internals.msgs[r.tid];if (promise) {promise.resolve(r.msg);delete self._internals.msgs[r.tid];} else {self.onmessage(r.msg);}};};//消息是一个json对象。self.send = async function (message) {return new Promise(function (resolve, reject) {var r = {tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).substr(0, 7), msg: message};self._internals.msgs[r.tid] = {resolve: resolve, reject: reject};self.ws.send(JSON.stringify(r));});};self.close = function () {self.ws && self.ws.close();self.ws = null;for (const tid in self._internals.msgs) {var promise = self._internals.msgs[tid];promise.reject('close');}};return self;
}
4.1 总体流程
学习资料分享
40voice · GitHub