CentOS版本号:7.9
SRS版本号:4.0.215
服务器IP:192.168.5.104
注意需要开启端口号:1935、1985、8000(UDP端口)、8080。
注意需要开启服务:http
文章目录
webRTC介绍
WebRTC是一个由Google发起的实时通讯解决方案,其中包含视频音频采集,编解码,数据传输,音视频展示等功能,我们可以通过技术快速地构建出一个音视频通讯应用。 虽然其名为WebRTC,但是实际上它不光支持Web之间的音视频通讯,还支持Android以及IOS端,此外由于该项目是开源的,我们也可以通过编译C++代码,从而达到全平台的互通。
getUserMedia
为一个 RTC 连接获取设备的摄像头与 (或) 麦克风权限,并为此 RTC 连接接入设备的摄像头与 (或) 麦克风的信号。
RTCPeerConnection
用于配置音频或视频聊天。
RTCDataChannel
用于设置两个浏览器之间的端到端 (en-US) 数据连接。
SRS流媒体服务器
SRS(Simple Realtime Server)是一个简单高效的实时视频服务器,支持RTMP/WebRTC/HLS/HTTP-FLV/SRT/GB28181。
SRS服务自带一个简单的信令服务器,用于webRTC交换SDP,提供了2个接口分别用于发布端(推流)和播放端(拉流)进行SDP交换
推流API:POST /rtc/v1/publish/
使用WebRTC推流到SRS时,需要先调用API交换SDP。例如:
POST/rtc/v1/publish/
Body inJSON:{"api":"https://d.ossrs.net/rtc/v1/publish/""streamurl":"webrtc://d.ossrs.net/live/3abd9f34","sdp":"v=0\r\n......\r\na=ssrc:2064016335 label:c8243ce9-ace5-4d17-9184-41a2543101b5\r\n"}
服务器响应对应的SDP如下:
{"code":0"sdp":"v=0\r\n......\r\na=candidate:1 1 udp 2130706431 172.18.0.4 8000 typ host generation 0\r\n""sessionid":"186tj710:hMub"}
假如部署SRS服务的服务器IP地址为:192.168.5.104,SRS的http_api监听端口为1985,则推流端交换SDP的API请求地址为:http://192.168.5.104:1985/rtc/v1/publish/,发送post请求
POST/rtc/v1/publish/
Body inJSON:{"api":"http://192.168.5.104:1985/rtc/v1/publish/""streamurl":"webrtc://192.168.5.104:8000/live/stream","sdp":"v=0\r\n......\r\na=ssrc:2064016335 label:c8243ce9-ace5-4d17-9184-41a2543101b5\r\n"}
拉流API:POST /rtc/v1/play/
拉流或播放时,需要调用另外的API,请求格式和publish一样。例如:
POST/rtc/v1/play/
Body inJSON:{"api":"https://d.ossrs.net/rtc/v1/play/""streamurl":"webrtc://d.ossrs.net/live/3abd9f34","sdp":"v=0\r\n......\r\na=ssrc:2064016335 label:c8243ce9-ace5-4d17-9184-41a2543101b5\r\n"}
服务器响应对应的SDP如下:
{"code":0"sdp":"v=0\r\n......\r\na=candidate:1 1 udp 2130706431 172.18.0.4 8000 typ host generation 0\r\n""sessionid":"186tj710:hMub"}
SRS服务配置开启RTC服务
创建web应用
编写代码,vue3项目
<template><div id="box"><!-- 设置自动播放,否则不会显示视频流画面 --><video id="video" autoplay></video><div id="btn"><button ref="button_one" @click="publish">开始直播</button><button ref="button_two" @click="close">停止直播</button><button ref="button_three" @click="stopAudio">关闭声音</button><button ref="button_four" @click="startAudio">开启声音</button><button ref="button_five" @click="play">播放直播</button></div><video id="video2" autoplay></video></div></template><script setup>import{ onMounted,ref }from'vue';// 定义全局属性let videoStream =null;let videoElement =null;// 全局的RTCPeerConnectionlet pc =null;// 全局音频轨道,用于RTCRtpSender发送和停止对应轨道let audioTrack =null;// 全局的RTCRtpSenderlet audioSender =null;// 获取按钮元素let button_one =ref(null);let button_two =ref(null);let button_three =ref(null);let button_four =ref(null);let button_five =ref(null);constpublish=async()=>{if(pc!==null&& pc!==undefined){
console.log("已开始推流");return;}var httpURL ="http://192.168.5.104:1985/rtc/v1/publish/";var webRTCURL ="webRTC://192.168.5.104/live/1";var constraints ={audio:{echoCancellation:true,// 回声消除noiseSuppression:true,// 降噪autoGainControl:true// 自动增益},video:{frameRate:{min:30},// 最小帧率width:{min:640,ideal:1080},// 宽度 height:{min:360,ideal:720},// 高度 aspectRadio:16/9// 宽高比}}// 通过摄像头、麦克风获取音视频流
videoStream =await navigator.mediaDevices.getUserMedia(constraints);// 获取video元素
videoElement = document.querySelector("#video")//video播放流数据
videoElement.srcObject = videoStream;// 静音
videoElement.volume=0;// 创建RTC连接对象
pc =newRTCPeerConnection();// RTCPeerConnection方法addTransceiver()创建一个新的RTCRtpTransceiver,并将其添加到与RTCPeerConnection关联的收发器集中。// 每个收发器代表一个双向流,RTCRtpSender和RTCRtpReceiver都与之相关联。// 注意添加顺序为audio、video,后续RTCPeerConnection创建offer时SDP的m线顺序遵循此顺序创建,SRS自带的信令服务器响应的SDP中m线总是先audio后video。// 若本端SDP和远端SDP中的m线顺序不一直,则设置远端描述时会异常,显示offer中的m线与answer中的m线顺序不匹配
pc.addTransceiver("audio",{direction:"recvonly"});
pc.addTransceiver("video",{direction:"recvonly"});// 遍历getUserMedia()获取到的流数据,拿到其中的音频轨道和视频轨道,加入到RTCPeerConnection连接的音频轨道和视频轨道中
videoStream.getTracks().forEach((track)=>{
pc.addTrack(track);});// 创建本端offervar offer =await pc.createOffer();// 设置本端await pc.setLocalDescription(offer);var data ={"api": httpURL,"streamurl":webRTCURL,"sdp":offer.sdp
}// SDP交换,请求SRS自带的信令服务器httpApi(httpURL,data).then(async(data)=>{
console.log("answer",data);// 设置远端描述,开始连接await pc.setRemoteDescription(newRTCSessionDescription({type:'answer',sdp: data.sdp}));
button_one.value.disabled=true;
button_two.value.disabled=false;
button_three.value.disabled=false;
button_five.value.disabled=false;}).catch((data)=>{if(data.code===400){
console.log("SDP交换失败");}});}constplay=async()=>{var httpURL ="http://192.168.5.104:1985/rtc/v1/play/";var webRTCURL ="webRTC://192.168.5.104/live/1";// 创建RTCPeerConnection连接对象var pc =newRTCPeerConnection();// 创建媒体流对象var stream =newMediaStream();// 获取播放流的容器videovar videoElement2 = document.querySelector("#video2");// 监听流
pc.ontrack=(event)=>{// 监听到的流加入MediaStream对象中让video播放
stream.addTrack(event.track);
videoElement2.srcObject = stream;}// RTCPeerConnection方法addTransceiver()创建一个新的RTCRtpTransceiver,并将其添加到与RTCPeerConnection关联的收发器集中。// 每个收发器代表一个双向流,RTCRtpSender和RTCRtpReceiver都与之相关联。// 注意添加顺序为audio、video,后续RTCPeerConnection创建offer时SDP的m线顺序遵循此顺序创建,SRS自带的信令服务器响应的SDP中m线总是先audio后video。// 若本端SDP和远端SDP中的m线顺序不一直,则设置远端描述时会异常,显示offer中的m线与answer中的m线顺序不匹配
pc.addTransceiver("audio",{direction:"recvonly"});
pc.addTransceiver("video",{direction:"recvonly"});var offer =await pc.createOffer();await pc.setLocalDescription(offer)var data ={"api": httpURL,"streamurl":webRTCURL,"sdp":offer.sdp
}// SDP交换,请求SRS自带的信令服务器httpApi(httpURL,data).then(async(data)=>{
console.log("answer",data);// 设置远端描述,开始连接await pc.setRemoteDescription(newRTCSessionDescription({type:'answer',sdp: data.sdp}));
button_five.value.disabled=true;}).catch((data)=>{if(data.code===400){
console.log("SDP交换失败");}});}// 关闭连接constclose=()=>{if(pc!==null&&pc!==undefined){
pc.close();
pc =null;
button_one.value.disabled=false;
button_two.value.disabled=true;
button_three.value.disabled=true;
button_four.value.disabled=true;
button_five.value.disabled=true;}}// 关闭音频conststopAudio=()=>{if(pc!==null&&pc!==undefined){// RTCPeerConnection方法getSenders()返回RTCRtpSender对象的数组,// 每个对象代表负责传输一个轨道的数据的RTP发送器。// sender对象提供了检查和控制音轨数据的编码和传输的方法和属性。
pc.getSenders().forEach((sender)=>{if(sender.track!==null&&sender.track.kind==="audio"){// 拿到音频轨道
audioTrack = sender.track;// 拿到音频轨道发送者对象RTCRtpSender
audioSender = sender;// RTCRtpSender的replaceTrack()可以在无需重新媒体协商的情况下用另一个媒体轨道更换当前正在发送轨道// 参数为空则将当前正在发送的轨道停止,比如关闭音频,再次开启时将音频轨道作为参数传入
audioSender.replaceTrack(null);
button_three.value.disabled=true;
button_four.value.disabled=false;}});}}// 开启音频conststartAudio=()=>{
console.log(audioSender);if(pc!==null&&pc!==undefined){if(audioSender.track===null){
audioSender.replaceTrack(audioTrack);
button_three.value.disabled=false;
button_four.value.disabled=true;}}}consthttpApi=(httpURL,data)=>{var promise =newPromise((resolve,reject)=>{var xhr =newXMLHttpRequest();
xhr.open('POST', httpURL,true);
xhr.setRequestHeader('Content-type','application/json');
xhr.send(JSON.stringify(data));
xhr.onload=()=>{if(xhr.readyState !== xhr.DONE)reject(xhr);if(xhr.status !==200&& xhr.status !==201)reject(xhr);var data =JSON.parse(xhr.responseText);if(data.code===0){resolve(data);}else{reject(data)}}});return promise;}onMounted(()=>{
button_one.value.disabled=false;
button_two.value.disabled=true;
button_three.value.disabled=true;
button_four.value.disabled=true;
button_five.value.disabled=true;});</script><style lang="scss">*{margin:0;padding:0;border:0;
box-sizing: border-box;}
#box{width:100%;
text-align: center;}
video{
background-color: black;width: 500px;height: 400px;
object-fit: cover;}
#btn{width:80%;height: 100px;display: flex;margin:10px 10%;}
button{flex:1;height: 100px;
background-color: aqua;
border-radius: 20px;
margin-left: 10px;}button:nth-child(1){
margin-left:0;}</style>
运行代码
2个客户端双向推拉流即可实现实时视频通话
版权归原作者 落丶寞 所有, 如有侵权,请联系我们删除。