~~~基于开源音视频服务组件SRS5实现一对一、一对多和视频会议功能~~~

一、前言

在工作生活中经常使用音视频通话功能,例如:微信视频聊天、钉钉在线视频会议、视频直播等,尤其现在短视频、直播带货比较火热,在线视频相关的需求也会多了起来,项目组也经常会遇到在线视频通话相关的需求,例如,医疗相关的在线问诊、安防相关的视频在线应急事件处理等;

二、WebRTC协议

WebRTC(Web Real-Time Communication)作为一项开放的实时通信标准,为开发者提供了快速构建实时音视频通话系统的能力;
WebRTC 是一整套 API,为浏览器、移动应用提供实时通信(RealTime Communications)能力。它包含了流媒体协议的功能,但是不是以协议的方式暴露给开发者的WebRTC 支持 Chrome 23+、Firefox 22+、Chrome for Android,提供 Java / Objective-C 绑定WebRTC 主要有三个职责

  • 捕获客户端音视频,对应接口 MediaStream(也就是 getUserMedia)
  • 音视频传输,对应接口 RTCPeerConnection
  • 任意数据传输,对应接口 RTCDataChannel

WebRTC 由 Google 开发,并在 2011 年首次发布。WebRTC 已经被广泛使用,并被许多知名网站和应用程序所采用,例如 Google Meet、Zoom、Skype 等。

WebRTC 使用了一系列技术来实现实时通信,包括:

  • SDP(Session Description Protocol):SDP
    用于描述通信会话的属性,例如参与者、协议、传输方式等。因此,参与通信双方要了解彼此支持的媒体格式,必须要交换SDP信息,而交换SDP的过程,通常称之为媒体协商
  • ICE(Interactive Connectivity Establishment):ICE 用于建立通信会话的连接。
  • STUN(Session Traversal Utilities for NAT):STUN 用于发现和绕过 NAT(网络地址转换)设备。
  • TURN(Traversal Using Relay NAT):TURN 用于在 NAT 设备后面建立通信会话。

WebRTC 内置了点对点的支持,也就是说流不一定需要经过服务器中转。

三、常见的音视频服务

为了快速开发和减少维护成本常使用第三方的即时通讯服务实现在线音视频服务,例如阿里的百川云旺腾讯云的即时通讯服务(IM)网易云信等,当前市面上有很多,之前在开发一个在线视频指挥调度项目使用就是使用的网易云信即时通讯功能实现在线音视频通话功能;
在这里插入图片描述
这里功能实现方便不用维护音视频服务器资源,但是三方音视频服务的缺点也很明显就是收费,二期长时期的费用还很高;
在这里插入图片描述
本着白嫖的精神首先会想到有没有免费开源的音视频服务器,肯定是有的,在网上找到两个使用人群比较的音视频服务器组件:SRSZLMediaKit

注:在使用网易云信使用音视频通话功能有一个比较坑的地方就是web端调用sdk接口只支持原生js,不支持reactjs、vue等框架;

1、SRS音视频服务

SRS是一个开源的(MIT协议)简单高效的实时视频服务器,支持RTMPWebRTCHLSHTTP-FLVSRTMPEG-DASHGB28181等协议。 SRS媒体服务器FFmpeg、OBS、VLC、 WebRTC等客户端配合使用,提供流的接收和分发的能力,是一个典型的发布 (推流)和订阅(播放)服务器模型。 SRS支持互联网广泛应用的音视频协议转换,比如可以将RTMP或SRT, 转成HLS或HTTP-FLV或WebRTC等协议。
在这里插入图片描述

SRS官网介绍

2、ZLMediaKit音视频服务

ZLMediaKit是一个基于C++开发的开源流媒体服务器。它提供了高性能的音视频处理能力,支持常见的流媒体协议,如RTSP、RTMP、HLS和HTTP-FLV,并且具有低延迟和高并发处理能力;支持多种协议(RTSP/RTMP/HLS/HTTP-FLV/WebSocket-FLV/GB28181/HTTP-TS/WebSocket-TS/HTTP-fMP4/WebSocket-fMP4/MP4/WebRTC),支持协议互转。
在这里插入图片描述
ZLMediaKit官网介绍

SRSZLMediaKit各有优势,本文主要介绍基于SRS实现一对一一对多音视频通话功能,关于ZLMediaKit的功能不在做过多介绍,有需求的自行了解。

四、SRS服务搭建

通过docker镜像搭建环境,搭建步骤在官网有详细介绍,位置如下:
在这里插入图片描述

本机启动SRS:

export CANDIDATE="192.168.1.10"
docker run --rm --env CANDIDATE=$CANDIDATE \
  -p 1935:1935 -p 8080:8080 -p 1985:1985 -p 8000:8000/udp \
  registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5 \
  objs/srs -c conf/rtc.conf
Note: 请将CANDIDATE设置为服务器的外网地址

本机启动信令:

docker run --rm -p 1989:1989 registry.cn-hangzhou.aliyuncs.com/ossrs/signaling:1

启动httpx-static,转换HTTPS和WSS协议:

export CANDIDATE="192.168.1.10"
docker run --rm -p 80:80 -p 443:443 registry.cn-hangzhou.aliyuncs.com/ossrs/httpx:1 \
    ./bin/httpx-static -http 80 -https 443 -ssk ./etc/server.key -ssc ./etc/server.crt \
          -proxy http://$CANDIDATE:1989/sig -proxy http://$CANDIDATE:1985/rtc \
          -proxy http://$CANDIDATE:8080/
Note: 请将CANDIDATE设置为服务器的外网地址

注:如果服务部署在阿里云或者腾讯云等云服务上,一定要映射相关端口,并注意是tcp协议还是udp协议,比如,8000端口就是UDP协议,本人在使用时把UDP协议设置为了TCP协议,导致获取远端视频失败,视频传输是UDP协议

五、一对一、一对多代码实现

一对一视频通话示例:
web端:
在这里插入图片描述

手机端:
在这里插入图片描述

基于SRS实现web端一对一一对多的音视频通话的样例在SRS源码中已经给出了,也是通过原生js实现的,位置如下图:
在这里插入图片描述
源码:

<!DOCTYPE html>
<html>
<head>
    <title>SRS</title>
    <meta charset="utf-8">
    <style>
        body{
            padding-top: 30px;
        }
    </style>
    <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
    <script type="text/javascript" src="js/jquery-1.12.2.min.js"></script>
    <script type="text/javascript" src="js/adapter-7.4.0.min.js"></script>
    <script type="text/javascript" src="js/srs.sdk.js"></script>
    <script type="text/javascript" src="js/srs.sig.js"></script>
</head>
<body>
<img src='https://ossrs.net/gif/v1/sls.gif?site=ossrs.net&path=/player/one2one'/>
<div class="navbar navbar-fixed-top">
    <div class="navbar-inner">
        <div class="container">
            <a class="brand" href="https://github.com/ossrs/srs">SRS</a>
            <div class="nav-collapse collapse">
                <ul class="nav srs_nav">
                    <li class="active"><a href="one2one.html">一对一通话</a></li>
                    <li><a href="room.html">多人通话</a></li>
                    <li class="srs_ignore">
                        <a href="https://github.com/ossrs/signaling">
                            <img alt="GitHub Repo stars" src="img/shields-io-signaling.svg">
                        </a>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</div>
<div class="container">
    <div class="form-inline">
        SRS:
        <input type="text" id="txt_host" class="input-medium" value="">
        Room:
        <input type="text" id="txt_room" class="input-small" value="live">
        Display:
        <input type="text" id="txt_display" class="input-small" value="">
        <button class="btn btn-primary" id="btn_start">开始通话</button>
    </div>

    <div class="row">
        <div class="span4 hide" id="publisher">
            <label></label>
            <video id="rtc_media_publisher" width="310" autoplay muted controls></video>

            <label></label>
            <span id='self'></span>
        </div>
        <div class="span6 hide" id="player">
            <label></label>
            <video id="rtc_media_player" width="310" autoplay muted controls></video>

            <label></label>
            <span id='peer'></span>
            <a href="javascript:control_refresh_peer()">Refresh</a>
            <input type="text" id="txt_alert" class="input-medium" value="">
            <a href="javascript:control_alert_peer()">Alert</a>
        </div>
    </div>

    <label></label>

    <div class="accordion hide srs_merge">
        <div class="accordion-group">
            <div class="accordion-heading">
                <a href="javascript:void(0)" class="accordion-toggle">FFmpeg合流转直播</a>
            </div>
            <div class="accordion-body collapse in">
                <div class="accordion-inner" style="overflow:auto">
                    ffmpeg -f flv -i rtmp://<span class="ff_host"></span>/<span class="ff_app"></span>/<span class="ff_first"></span> -f flv -i rtmp://<span class="ff_host"></span>/<span class="ff_app"></span>/<span class="ff_second"></span> \ <br/>
                    &nbsp;&nbsp;&nbsp;&nbsp; -filter_complex "[1:v]scale=w=96:h=72[ckout];[0:v][ckout]overlay=x=W-w-10:y=H-h-10[out]" -map "[out]" \ <br/>
                    &nbsp;&nbsp;&nbsp;&nbsp; -c:v libx264 -profile:v high -preset medium \ <br/>
                    &nbsp;&nbsp;&nbsp;&nbsp; -filter_complex amix -c:a aac \ <br/>
                    &nbsp;&nbsp;&nbsp;&nbsp; -f flv -y
                    <span id="ff_output">
                        rtmp://<span class="ff_host"></span>/<span class="ff_app"></span>/merge
                    </span>
                    <span id="ff_wxvideo"></span>
                    && <br/>
                    echo "ok"
                </div>
                <div class="accordion-inner">
                    <a href="#" id="ff_preview" target="_blank" class="accordion-toggle">
                        预览:rtmp://<span class="ff_host"></span>/<span class="ff_app"></span>/merge
                    </a>
                </div>
            </div>
        </div>
    </div>

    <label></label>

    <div class="accordion hide srs_merge">
        <div class="accordion-group">
            <div class="accordion-heading">
                <a href="javascript:void(0)" class="accordion-toggle">
                    视频号推流信息
                </a>
            </div>
            <div class="accordion-body collapse in">
                <div class="accordion-inner">
                    推流地址 <input type="text" id="txt_wx_video_tcurl" class="input-xxlarge">
                </div>
                <div class="accordion-inner">
                    推流密钥 <input type="text" id="txt_wx_video_stream" class="input-xxlarge">
                </div>
                <div class="accordion-inner">
                    <button class="btn btn-primary" id="btn_apply">应用</button>
                </div>
            </div>
        </div>
    </div>
</div>
<footer class="footer">
    <div class="container">
            <p>&copy; SRS 2020</p>
    </div>
</footer>
<script type="text/javascript">
    var sig = null;
    var publisher = null;
    var player = null;
    var control_refresh_peer = null;
    var control_alert_peer = null;
    $(function(){
        console.log('?wss=x to specify the websocket schema, ws or wss');
        console.log('?wsh=x to specify the websocket server ip');
        console.log('?wsp=x to specify the websocket server port');
        console.log('?host=x to specify the SRS server');
        console.log('?room=x to specify the room to join');
        console.log('?display=x to specify your nick name');

        var startDemo = async function () {
            var host = $('#txt_host').val();
            var room = $('#txt_room').val();
            var display = $('#txt_display').val();

            // Connect to signaling first.
            if (sig) {
                sig.close();
            }
            sig = new SrsRtcSignalingAsync();
            sig.onmessage = function (msg) {
                console.log('Notify: ', msg);

                if (msg.event === 'leave') {
                    $('#player').hide();
                }

                if (msg.event === 'publish') {
                    if (msg.peer && msg.peer.publishing && msg.peer.display !== display) {
                        startPlay(host, room, msg.peer.display);
                    }
                }

                if (msg.event === 'control') {
                    if (msg.param === 'refresh') {
                        setTimeout(function () {
                            window.location.reload();
                        }, 500);
                    } else if (msg.param === 'alert') {
                        alert('From ' + msg.peer.display + ': ' + msg.data);
                    }
                }

                if (msg.participants.length >= 2) {
                    $('.srs_merge').show();
                } else {
                    $('.srs_merge').hide();
                }
            };
            await sig.connect(conf.wsSchema, conf.wsHost, room, display);

            control_refresh_peer = async function () {
                let r1 = await sig.send({action:'control', room:room, display:display, call:'refresh'});
                console.log('Signaling: control peer to refresh ok', r1);
            };
            control_alert_peer = async function () {
                let r1 = await sig.send({action:'control', room:room, display:display, call:'alert', data:$('#txt_alert').val()});
                console.log('Signaling: control peer to alert ok', r1);
            };

            let r0 = await sig.send({action:'join', room:room, display:display});
            console.log('Signaling: join ok', r0);

            // For one to one demo, alert and ignore when room is full.
            if (r0.participants.length > 2) {
                alert('Room is full, already ' + (r0.participants.length - 1) + ' participants');
                sig.close();
                return;
            }

            // Start publish media if signaling is ok.
            await startPublish(host, room, display);
            let r1 = await sig.send({action:'publish', room:room, display:display});
            console.log('Signaling: publish ok', r1);

            // Play the stream already in room.
            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_media_publisher').prop('srcObject', publisher.stream);

            return publisher.publish(url).then(function(session){
                $('#self').text('Self: ' + url);
            }).catch(function (reason) {
                publisher.close();
                $('#rtc_media_publisher').hide();
                console.error(reason);
            });
        };

        var startPlay = function (host, room, display) {
            $(".ff_second").each(function(i,e) {
                $(e).text(display);
            });

            var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query;
            $('#rtc_media_player').show();
            $('#player').show();

            if (player) {
                player.close();
            }

            player = new SrsRtcPlayerAsync();
            $('#rtc_media_player').prop('srcObject', player.stream);

            player.play(url).then(function(session){
                $('#peer').text('Peer: ' + display);
                $('#rtc_media_player').prop('muted', false);
            }).catch(function (reason) {
                player.close();
                $('#rtc_media_player').hide();
                console.error(reason);
            });
        };

        // Pass-by to SRS url.
        let conf = SrsRtcSignalingParse(window.location);
        $('#txt_host').val(conf.host);
        conf.room && $('#txt_room').val(conf.room);
        $('#txt_display').val(conf.display);

        $(".ff_host").each(function(i,e) {
            $(e).text(conf.host);
        });
        $(".ff_app").each(function(i,e) {
            $(e).text($('#txt_room').val());
        });
        $('#ff_preview').attr('href', 'http://ossrs.net/players/srs_player.html?app=' + $('#txt_room').val() + '&stream=merge.flv&server=' + conf.host + '&vhost=' + conf.host + '&autostart=true');

        // Update href for all navs.
        $('ul.srs_nav').children('li').not('.srs_ignore').children('a').each(function (i, e) {
            $(e).attr('href', $(e).attr('href') + conf.rawQuery);
        });

        $('#btn_apply').click(function () {
            if ($('#txt_wx_video_tcurl').val() !== '' && $('#txt_wx_video_stream').val() !== '') {
                $('#ff_wxvideo').text('"' + $('#txt_wx_video_tcurl').val() + $('#txt_wx_video_stream').val() + '"').show();
                $('#ff_output').hide();
                $('#ff_preview').parent().hide();
            } else {
                $('#ff_wxvideo').hide();
                $('#ff_output').show();
                $('#ff_preview').parent().show();
            }
        });

        $("#btn_start").click(startDemo);
        // Never play util windows loaded @see https://github.com/ossrs/srs/issues/2732
        if (conf.autostart) {
            window.addEventListener("load", function(){ startDemo(); });
        }
    });
</script>
</body>
</html>

六、总结

更好的使用技术,以熟悉技术为前提!!!基于SRS实现一对一、一对多实现音视频通话的js实现的业务逻辑的分析,后面文章介绍!!!

----------------------------------👇👇👇注:更多信息请关注公众号获取👇👇👇--------------------------------------------

【内容描述】 全套视频会议平台,并包含类似qq的点对点即时通讯系统,视频语音,电子白板,远程控制等强大功能。可提供全部源代码。详细功能描述如下: 视频会议特性:点输入输出,每个客户端最大可支持4通道输入(音视频同步)。每个输入通道可以被其他客户端选择性的接收。每个客户端最大可支持4个屏幕输出,可以将个输出屏幕组合成电视墙来使用。视频会议系统同即时通讯系统高度集成。通过即时通讯系统,可以很方便的邀请好友加入到视频会议中。 云台支持:支持各类主流云台,可以远程控制云台运动。 虚拟会议中心:单个服务器支持个虚拟视频会议。所有的会议都可以进行,彼此之间相互独立互不干扰。 会议模式权限控制:视频会议系统拥有种会议模式级权限控制,使得会议控制更加安全可靠。参加会议的用户有三种身份:主持人、与会者旁听者。主持人拥有全部的操作权限,同时负责会议的管理工作。与会者拥有指定操作的权限,该指定权限可以预先设置,也可以由主持人随时动态调整。旁听者没有任何操作权限。与会者可以请求发言,一旦成为发言者,该与会者即拥有全部的操作权限。会议模式包含自由发言模式受控模式。在受控模式下,与会者的发言请求需要主持人的批准;在自由发言模式下,与会者的发言请求不需要主持人的批准而立刻被允许。会议允许个用户拥有主持人身份,他们可以同时协同操作,使得会议的管理工作更加容易。 高质量的视音频,系统支持种类型的视频音频输入。视频输入设备支持标准的Windows摄像头专业的视频采集卡。系统支持种视频大小编码方式。视频尺寸支持从160X120到720X576。视频编码支持XVIDH264。系统支持种音频编码方式,最高音质可与CD相媲美。音频视频参数可以随时动态调整。主持人用户还可以远程调整其他用户的视音频参数,以帮助对系统不熟悉的用户。屏幕应用程序共享实时共享文、图片、网页、媒体文甚至整个桌面。轻松解决了会议中不同用户身处不同的地方的合作性困难,大大增强了视频会议系统的可用性,并确保其达到最佳的交互效果。 电子白板*使用矢量位图的格式,您可以保存、导入、粘贴位图或文本文。提供种的二维绘图模型:线、矩形、三角形、圆形、椭圆等,提供种三维模型:圆锥、圆柱及立方体等。仅仅通过鼠标操作,你就可以任意缩放、旋转其中的任何图形模型。白板操作即时被传送到所有会议用户,所有会议用户的白板将同步显示相同内容。系统支持电子白板的录制回放。 会议字幕*会议字幕将在所有会议用户的屏幕下方滚动显示。 文字聊天*系统支持所有会议用户之间的文字聊天。*系统支持两个会议用户之间私下的文字聊天,不会对会议其他用户产生干扰。 丰富的显示模板*系统支持种显示模板选择。*系统图像支持任意拖放。*系统独有画中画显示功能,可以重点突出被选择的视频图像。 录像回放*系统支持将会议的全部音频、视频以及数据操作录制在一个文中,回放的时候可以真实再现当时会议场景。*系统支持在会议中回放预先录制的文。 高效的传输算法*通过独立自主研发的传输算法,系统可以适应从56K拨号上网到光纤等各类IP网络。传输以保证实时性为最高目标,在保证实时性的前提下尽量获取最大传输带宽,这样可以最大程度的保障会议的效果。良好的扩展性*为满足大系统的容量要求,视频会议支持MCU级联实现。 即时通讯主要特性: 音视频*可以根据网络带宽情况调整音视频参数,以获得最佳效果。*可以远程调整其他用户的视音频参数,以帮助对系统不熟悉的用户。 电子白板*使用矢量位图的格式,您可以保存、导入、粘贴位图或文本文。提供种的二维绘图模型:线、矩形、三角形、圆形、椭圆等,提供种三维模型:圆锥、圆柱及立方体等。仅仅通过鼠标操作,你就可以任意缩放、旋转其中的任何图形模型。白板操作即时被传送到所有会议用户,所有会议用户的白板将同步显示相同内容。系统支持电子白板的录制回放。方视频*你可以邀请个用户加入到同一个视频对话中来组建一个快速的小型视频会议。屏幕应用程序共享*实时共享文、图片、网页、媒体文甚至整个桌面。轻松解决了会议中不同用户身处不同的地方的合作性困难。文传输*你可以传输任何文至任意用户。 网络文柜*你可以上传文至服务器并且可以被授权用户下载。发布公告*你可以对你所属部门的全体用户发布公告通知。与视频会议相集成*与视频会议系统相集成。通过即时通讯系统,你可以邀请其他用户加入到视频会议中来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值