SRS流媒体服务(四)WebRTC实现实时视频通话和低延时互动直播

news2024/11/17 15:32:19

CentOS版本号:7.9
在这里插入图片描述
SRS版本号:4.0.215
在这里插入图片描述
服务器IP:192.168.5.104
在这里插入图片描述
注意需要开启端口号:1935、1985、8000(UDP端口)、8080。
在这里插入图片描述
注意需要开启服务:http
在这里插入图片描述

文章目录

  • webRTC介绍
    • getUserMedia
    • RTCPeerConnection
    • RTCDataChannel
  • SRS流媒体服务器
    • 推流API:POST /rtc/v1/publish/
    • 拉流API:POST /rtc/v1/play/
    • SRS服务配置开启RTC服务
  • 创建web应用
    • 编写代码,vue3项目
    • 运行代码

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 in JSON:

{
  "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 in JSON:

{
  "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 in JSON:

{
  "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;
// 全局的RTCPeerConnection
let pc = null;
// 全局音频轨道,用于RTCRtpSender发送和停止对应轨道
let audioTrack = null;
// 全局的RTCRtpSender
let 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);

const publish = 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 = new RTCPeerConnection();
        
        // 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);
        });
        // 创建本端offer
        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(new RTCSessionDescription({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交换失败");
            }
        });
        
    
}

const play = async()=>{
    var httpURL = "http://192.168.5.104:1985/rtc/v1/play/";
    var webRTCURL = "webRTC://192.168.5.104/live/1";
    // 创建RTCPeerConnection连接对象
    var pc = new RTCPeerConnection();
    // 创建媒体流对象
    var stream = new MediaStream();
    // 获取播放流的容器video
    var 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(new RTCSessionDescription({type: 'answer', sdp: data.sdp}));
            button_five.value.disabled=true;

    }).catch((data)=>{
            if(data.code===400){
                console.log("SDP交换失败");
            }
    });
}

// 关闭连接
const close = ()=>{
    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;
    }
}
// 关闭音频
const stopAudio = ()=>{
    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;
            }
        });
    }
}
// 开启音频
const startAudio = ()=>{
    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;
       }
    }
}

const httpApi = (httpURL,data)=>{
    var promise = new Promise((resolve,reject)=>{
        var xhr = new XMLHttpRequest();
        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个客户端双向推拉流即可实现实时视频通话

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/890043.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

编译老版本c++程序 报错 msvcrt.dll 以及 0x000000 内存 不能为 “read“ 问题已解决

一般 win10 编译 xp对应老版本软件 调试采用 虚拟机形式进行测试&#xff0c;但是虚拟机中&#xff0c;无独立显卡&#xff0c;运行程序提示有&#xff0c;无法调用动态库&#xff0c;或者 内存无法读取&#xff0c;炸一看以为 winxp32位 内存识别只能3.7G.其实是显存无法使用…

【网工常用的CMD窗口命令行,你一定得记的】

作为网络工程师&#xff0c;工作中经常是容易被作为甩锅的对象。凡是业务系统出现问题或其他各种问题&#xff0c;人们总是会认为是网络问题&#xff0c;最初的怀疑都指向网工。然而最终结果往往是自己服务器出了问题。 现在我将列举几种我在日常工作中经常使用的查看网络状态…

七月 NFT 行业解读:游戏和音乐 NFT 引领增长,Opepen 掀起热潮

作者&#xff1a;lesleyfootprint.network 2023 年 7 月&#xff0c;NFT 市场的波动性持续存在&#xff0c;交易量呈下降趋势。然而&#xff0c;游戏和音乐 NFT 等领域的增长引人注目。参与这些细分领域的独立用户数量不断增加&#xff0c;反映了这些领域的复苏。 本综合报告…

实时会话简易版

1、数据存储 Redis缓存、pgsql数据库 2、存储使用 2.1、Redis缓存 1&#xff09;无序集合set&#xff1a;存储未读会话id 2&#xff09;list&#xff08;左进右出&#xff09;&#xff1a;存储会话未读消息 2.2、pgsql数据库 存储用户信息&#xff0c;存储会话id&#…

Maven之mirrorof范围

mirrorOf 是 central 还是 * 的问题 在配置阿里对官方中央仓库的镜像服务器时&#xff0c;我们使用到了 <mirror> 元素。 <mirror><id>aliyunmaven</id><mirrorOf>central</mirrorOf><name>阿里云公共仓库</name><url>…

【计算机视觉|生成对抗】非配对图像到图像的翻译:使用循环一致对抗网络(CycleGAN)

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Unpaired Image-to-Image Translation Using Cycle-Consistent Adversarial Networks 链接&#xff1a;[1703.10593] Unpaired Image-to-Image Translation using Cycle-Consistent Adver…

基于metrics-server弹性伸缩

目录 Kubernetes部署方式 基于kubeadm部署K8S集群 一、环境准备 1.1、主机初始化配置 1.2、部署docker环境 二、部署kubernetes集群 2.1、组件介绍 2.2、配置阿里云yum源 2.3、安装kubelet kubeadm kubectl 2.4、配置init-config.yaml 2.5、安装master节点 2.6、安装…

2022年09月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;统计误差范围内的数 统计一个整数序列中与指定数字m误差范围小于等于X的数的个数。 时间限制&#xff1a;5000 内存限制&#xff1a;65536 输入 输入包含三行&#xff1a; 第一行为N&#xff0c;表示整数序列的长度(N < 100); 第二行为N个整数&#xff0c;…

在C中使用Socket实现多线程异步TCP消息发送

目录 基础知识开始实现主要函数说明结束语 在本篇文章中&#xff0c;我们会探讨如何在C语言中使用socket来实现多线程&#xff0c;异步发送TCP消息的系统。虽然C标准库并没有原生支持异步和多线程编程&#xff0c;但是我们可以结合使用POSIX线程&#xff08;pthread&#xff09…

React2023电商项目实战 - 1.项目搭建

古人学问无遗力&#xff0c;少壮工夫老始成。 纸上得来终觉浅&#xff0c;绝知此事要躬行。 —— 陆游《《冬夜读书示子聿》》 系列文章目录 项目搭建App登录及网关App文章自媒体平台&#xff08;博主后台&#xff09;内容审核(自动) 文章目录 系列文章目录一、项目介绍1.页面…

二维码网络钓鱼攻击泛滥!美国著名能源企业成主要攻击目标

近日&#xff0c;Cofense发现了一次专门针对美国能源公司的网络钓鱼攻击活动&#xff0c;攻击者利用二维码将恶意电子邮件塞进收件箱并绕过安全系统。 Cofense 方面表示&#xff0c;这是首次发现网络钓鱼行为者如此大规模的使用二维码进行钓鱼攻击&#xff0c;这表明他们可能正…

云计算——ACA学习 云计算核心技术

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 写在前面 本系列将会持续更新云计算阿里云ACA的学习&#xff0c;了解云计算及网络安全相关…

使用 AI 将绘画和照片转换为动画

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑器的3D应用场景 华盛顿大学和Facebook的研究人员最近发表了一篇论文&#xff0c;展示了一种基于深度学习的系统&#xff0c;可以将静止图像和绘画转换为动画。称为照片唤醒的算法使用卷积神经网络从单个静止图像以 …

性能分析之MySQL慢查询日志分析(慢查询日志)

一、背景 MySQL的慢查询日志是MySQL提供的一种日志记录,他用来记录在MySQL中响应的时间超过阈值的语句,具体指运行时间超过long_query_time(默认是10秒)值的SQL,会被记录到慢查询日志中。 慢查询日志一般用于性能分析时开启,收集慢SQL然后通过explain进行全面分析,一…

【STM32】FreeRTOS事件组学习

事件组&#xff08;Event Group&#xff09; 一个任务执行之前需要经过多个条件进行判断&#xff0c;当条件全部满足或多个条件中的某一个条件满足才执行。 实验&#xff1a;创建两个任务&#xff0c;一个事件组&#xff0c;当按键一二三都按过一遍才打印。 实现&#xff1a…

【论文阅读】SHADEWATCHER:使用系统审计记录的推荐引导网络威胁分析(SP-2022)

SHADEWATCHER: Recommendation-guided CyberThreat Analysis using System Audit Records S&P-2022 新加坡国立大学、中国科学技术大学 Zengy J, Wang X, Liu J, et al. Shadewatcher: Recommendation-guided cyber threat analysis using system audit records[C]//2022 I…

python3 0学习笔记之基本知识

0基础学习笔记之基础知识 &#x1f4da; 基础内容1. 条件语句 if - elif - else2. 错误铺捉try - except(一种保险策略&#xff09;3. 四种开发模式4. 函数&#xff1a;def用来定义函数的5. 最大值最小值函数&#xff0c;max &#xff0c;min6. is 严格的相等&#xff0c;is no…

【Linux命令详解 | gzip命令】 gzip命令用于压缩文件,可以显著减小文件大小

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 基本压缩和解压2. 压缩目录3. 查看压缩文件内容4. 测试压缩文件的完整性5. 强制压缩6. 压缩级别7. 与其他命令结合使用8. 压缩多个文件9. 自动删除原文件 总结 简介 在Linux中&#xff0c;gzip命令是一款强大的文…

使用grep做文本的过滤

常与 管道符&#xff08;|&#xff09;结合在一起使用 管道符 piping&#xff1a;用于前一个命令的输出当作后一个命令的输入。常用于连接多个命令 ┌──(root㉿kali)-[~/work/exam] └─# ps aux | grep apache2 root 41946 0.0 0.2 6568 2304 pts/1 S 17:26…

怎么对mp4视频进行压缩?分享了几个不错的方法

怎么对mp4视频进行压缩&#xff1f;这个问题非常重要。确实&#xff0c;MP4视频文件由于包含音频和图像&#xff0c;通常会占据较大的存储空间。如果我们在手机或电脑上保存过多的MP4视频文件&#xff0c;随着时间的积累&#xff0c;会导致存储容量不足的问题。另外&#xff0c…