WebRTC音视频通话(二)简单音视频通话
本篇不详细介绍websocket,只针对websocket整合rtc。
一、简单说下webrtc的流程
webrtc是P2P通信,也就是实际交流的只有两个人,而要建立通信,这两个人需要交换一些信息来保证通信安全。而且,webrtc必须通过ssh加密,也就是使用https协议、wss协议。
借用一幅图
1.1 创建端点的解析
以下解析不包括websockt,只针对stun做解析。与上图略有不同
首先,Client A创建端点(Create PeerConnection),并添加音视频流(Add Streams)。接下来通知Client B,让Client B也创建一个端点。
Client B收到通知后,Client B创建端点(Create PeerConnection),并添加音视频流(Add Streams),
接下来,Client B创建一个用于answer的SDP对象(Create Answer),保存并发送给Client A。
Client A收到用于answer的SDP后,保存下来。
然后, Client A创建一个用于offer的SDP对象(Create Office),保存并发送给Client B。
最后,Client B保存收到的用于offer的SDP对象
以上步骤完成之后:
1、rtc会自动收集Candidate信息,并通过回调函数通知你,用于交换Candidate信息。
2、交换完Candidate信息后,P2P连接就建立好了。并通过回调函数,将远程视频流给你
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
1.2 交换Candidate信息
Candidate信息是交换完SDP对象之后,自动收集的信息。在创建端点(PeerConnection)的时候,添加回调函数(onIceCandidate)
创建回调函数(onIceCandidate)
将Candidate信息发送给另一端(a发给b,b发给a)
保存发过来的 Candidate信息(addIceCandidate)。注意是保存发过来的,不是保存自己的!!!
交换完Candidate信息后,P2P连接就建立好了。
二、新建springboot项目,并开启ssh
因为rtc必须使用ssh,所以springboot需要使用https协议才可以
2.1 生成ssh自签名文件
在终端中执行
keytool -genkey -alias webrtc -dname "CN=Andy,OU=kfit,O=kfit,L=HaiDian,ST=BeiJing,C=CN" -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore webrtc.keystore -validity 36500
执行时,会要求输入密码;
执行后,会在根目录下生成一个webrtc.keystore的文件
2.2 配置ssh信息
将webrtc.keystore文件放在resource目录下
在application.yaml中填写配置信息
server:ssl:#证书的路径key-store: classpath:webrtc.keystore#证书密码key-store-password: 123456#秘钥库类型key-store-type: JKS
2.3 检测一下能不能跑起来
运行就行,能跑起来就OK。
三、编写websocket服务类
这个简单的demo只考虑一个房间,没有房间控制,所以websocket代码很少,主要代码都在js里面。
3.1 先放一下Message实体类
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Message
{private String operation;private Object msg;public Message setOperation(String operation){this.operation = operation;return this;}public Message setMsg(Object msg){this.msg = msg;return this;}public String getMsgStr(){return msg == null ? "" : msg.toString();}
}
3.2 服务类
主要有以下信息:
- 加入房间(into)
- 发送 sdp 对象(send-sdp)
- 交换 candidate 信息(send-candidate)
package com.websocket.controller;import com.alibaba.fastjson.JSONObject;
import com.websocket.pojo.Message;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;@Log4j2
@Controller
@ServerEndpoint("/webrtc")
public class WebrtcController
{/*** 这里只做一个最简单的, 只有一个房间, 后面有需要自己可以改*/private static Session offer;private static Session answer;@OnMessagepublic void onMessage(Session session, String message){final Message data = JSONObject.parseObject(message, Message.class);final Message response = Message.builder().operation(data.getOperation()).build();switch (data.getOperation()) {//加入房间case "into": {if (offer == null) {offer = session;response.setMsg("offer");}else if (answer == null) {answer = session;response.setMsg("answer");}else {response.setMsg("none");}sendMessage(session, response);break;}case "start":sendMessage(offer, response);break;//发送 offer 的 SDP 对象case "send-offer"://发送 answer 的 SDP 对象case "send-answer"://交换 candidate 信息case "send-candidate": {sendOther(session, response.setMsg(data.getMsg()));break;}}}@OnClosepublic void onClose(Session session, CloseReason closeReason){if (offer != null && session.getId().equals(offer.getId())) {offer = null;}else if (answer != null && session.getId().equals(answer.getId())) {answer = null;}}public static void sendOther(Session session, Object msg){if (offer != null && session.getId().equals(offer.getId())) {sendMessage(answer, msg);}else if (answer != null && session.getId().equals(answer.getId())) {sendMessage(offer, msg);}}public static void sendMessage(Session session, Object msg){sendMessage(session, JSONObject.toJSONString(msg));}@SneakyThrowsprivate static void sendMessage(Session session, String msg){final RemoteEndpoint.Basic basic = session.getBasicRemote();basic.sendText(msg);}
}
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
四、页面
4.1 html
这部分不太重要,就直接放上来了
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head><meta charset="UTF-8"><title>websocket-demo</title><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body><div class="container py-3"><div class="row"><div class="col-12"><div id="addRoom" class="btn btn-primary">加入房间</div></div><div class="col-12 col-lg-6"><p>本地视频:</p><video id="localVideo" width="500px" height="300px" autoplay style="border: 1px solid black;"></video></div><div class="col-12 col-lg-6"><p>远程视频:</p><video id="remoteVideo" width="500px" height="300px" autoplay style="border: 1px solid black;"></video></div></div></div><script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js" type="text/javascript"></script></body>
</html>
4.2 webrtc工具类 webrtc-util.js
包括以下方法:
打开本地音视频流
创建PeerConnection对象
创建用于office的SDP对象
创建用于answer的SDP对象
保存SDP对象
保存Candidate信息
收集 candidate 的回调
获得远程视频流的回调
需要注意的是:最后的两个回调,需要在创建PeerConnection对象之后,打开本地音视频流之前执行。
4.2.1 本地变量
其中的 ice对象,根据上一篇测试通过的stun服务器信息填写。
//端点对象
let rtcPeerConnection;//本地视频流
let localMediaStream = null;//ice服务器信息, 用于创建 SDP 对象
let iceServers = {"iceServers": [// {"url": "stun:stun.l.google.com:19302"},{"urls": ["stun:159.75.239.36:3478"]},{"urls": ["turn:159.75.239.36:3478"], "username": "chr", "credential": "123456"},]
};// 本地音视频信息, 用于 打开本地音视频流
const mediaConstraints = {video: {width: 500, height: 300},audio: true //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
};// 创建 offer 的信息
const offerOptions = {iceRestart: true,offerToReceiveAudio: true, //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
};
4.2.2 打开本地音视频流
// 1、打开本地音视频流
const openLocalMedia = (callback) => {console.log('打开本地视频流');navigator.mediaDevices.getUserMedia(mediaConstraints).then(stream => {localMediaStream = stream;//将 音视频流 添加到 端点 中for (const track of localMediaStream.getTracks()) {rtcPeerConnection.addTrack(track, localMediaStream);}callback(localMediaStream);})
}
4.2.3 创建 PeerConnection 对象
// 2、创建 PeerConnection 对象
const createPeerConnection = () => {rtcPeerConnection = new RTCPeerConnection(iceServers);
}
4.2.4 创建用于 offer 的 SDP 对象
// 3、创建用于 offer 的 SDP 对象
const createOffer = (callback) => {// 调用PeerConnection的 CreateOffer 方法创建一个用于 offer的SDP对象,SDP对象中保存当前音视频的相关参数。rtcPeerConnection.createOffer(offerOptions).then(sdp => {// 保存自己的 SDP 对象rtcPeerConnection.setLocalDescription(sdp).then(() => callback(sdp));}).catch(() => console.log('createOffer 失败'));
}
4.2.5 创建用于 answer 的 SDP 对象
// 4、创建用于 answer 的 SDP 对象
const createAnswer = (callback) => {// 调用PeerConnection的 CreateAnswer 方法创建一个 answer的SDP对象rtcPeerConnection.createAnswer(offerOptions).then(sdp => {// 保存自己的 SDP 对象rtcPeerConnection.setLocalDescription(sdp).then(() => callback(sdp));}).catch(() => console.log('createAnswer 失败'))
}
4.2.6 保存远程的 SDP 对象
// 5、保存远程的 SDP 对象
const saveSdp = (answerSdp, callback) => {rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(answerSdp)).then(callback);
}
4.2.7 保存 candidate 信息
// 6、保存 candidate 信息
const saveIceCandidate = (candidate) => {let iceCandidate = new RTCIceCandidate(candidate);rtcPeerConnection.addIceCandidate(iceCandidate).then(() => console.log('addIceCandidate 成功'));
}
4.2.8 收集 candidate 的回调
// 7、收集 candidate 的回调
const bindOnIceCandidate = (callback) => {// 绑定 收集 candidate 的回调rtcPeerConnection.onicecandidate = (event) => {if (event.candidate) {callback(event.candidate);}};
};
4.2.9 获得 远程视频流 的回调
// 8、获得 远程视频流 的回调
const bindOnTrack = (callback) => {rtcPeerConnection.ontrack = (event) => callback(event.streams[0]);
};
以上代码都写在 webrtc-util.js 中,是可以复用滴
接下来,就是在html中引入这个js,然后和websocket整合一下,把通知、candidate 信息等等,通过websocket发送给另一端。
End
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
WebRTC音视频通话(二)简单音视频通话相关推荐
- WebRTC 实现P2P音视频通话——实现一对一音视频通话
WebRTC 实现P2P音视频通话 WebRTC 实现P2P音视频通话--搭建信令服务器 WebRTC 实现P2P音视频通话--搭建stun/trun P2P穿透和转发服务器 WebRTC 实现P2P ...
- 一文学会 QQ视频通话、抖音的视频回显实现方案
QQ视频通话.抖音的视频回显 是如何实现的 先说为什么会有这一篇文章: 2014年联想曾经做过一款 短视频软件,叫"魔力秀".可以说和现在的抖音基本是一样的,但因为"魔力 ...
- webrtc 入门第五章 一对一视频通话实现
webrtc 入门第五章 一对一视频通话实现 一.介绍 在前面的章节我们学习了如何操作本地的设备摄像头,麦克风等,学会了如何进行本地的流媒体操作如录制,下载,同步等.在第三第四章节学习了webrt ...
- 入门启发:音视频的简单理解
算机技术领域中,『音视频技术』应该说算是较复杂的小门类.较复杂的东西有个简单的入门指引,或者有前辈带路是很重要的. 前阵子,因为项目中急需音视频技术,虽然网上资料看似很丰富,但对初学者来说,很多资料都 ...
- 音视频开发二:音视频知识总结
文章目录 简介 简单理解,音视频原理 音视频理论基础 音频 声音介绍 **为什么要存在数字音频 ?** **什么是数字音频?** 从"模拟信号"到"数字化"的过 ...
- JavaWeb-SpringBoot(抖音)_二、服务器间通讯
JavaWeb-SpringBoot(抖音)_一.抖音项目制作 传送门 JavaWeb-SpringBoot(抖音)_二.服务器间通讯 传送门 JavaWeb-SpringBoot(抖音)_三.抖音项 ...
- 不若与众:说说抖音的二创激励计划
七月份抖音与爱奇艺宣布达成合作,如今,抖音二创激励计划落地.9月5日,抖音公布二创激励计划,相当于为这场燎原之火又添了一大把柴. 此次计划是抖音与爱奇艺双方围绕长视频的二创与推广展开的探索,面向影视综 ...
- 音视频开发成长之路—进阶之路3个重要知识点丨WebRTC丨FFmpeg丨SRS流媒体服务器丨C++音视频丨嵌入式音视频
音视频开发成长之路-进阶之路3个重要知识点 视频讲解如下,点击观看: 音视频开发成长之路-进阶之路3个重要知识点丨WebRTC丨FFmpeg丨SRS流媒体服务器丨C++音视频丨嵌入式音视频 音视频高级 ...
- (二)音视频:MediaCodec编码桌面信息 完整Demo 进一步理解H264
(一)音视频:解码H264文件流程 渲染和拿到解码后源数据YUV 完整Demo] (二)音视频:MediaCodec编码桌面信息 完整Demo 进一步理解H264 (三)音视频:解析H264 SPS ...
- WebRTC系列<二> 案例与工具
阅读关于webRTC的其他文章: WebRTC系列<一> 什么是WebRTC? WebRTC系列<二> 案例与工具 ----------------------------- ...
最新文章
- Linux教程 网络管理命令Netstat的使用
- Mycat简单实现读写分离与分库分表
- python3.5和pip3安装路径不匹配问题
- mvc php session,PHP Session入门教程
- bootstrap之排版类
- JMX和Spring –第1部分
- 微信小程序和vue双向绑定哪里不一样_浅析Vue 和微信小程序的区别、比较
- P2158 [SDOI2008]仪仗队 欧拉函数
- python 新式类和旧式类
- Android SDK下载网址
- c语言画bode图程序,根据上位机测得的Bode图的幅频特性,就能确定系统(或环节)的相频特性,试问这在什么系统时才能实现?...
- [BZOJ 5339] 教科书般的亵渎
- 《微电子概论》2.1 理论基础
- 【C#】委托,方法回调,匿名函数,拉姆达表达式
- 介绍一个使用go写的TUI性能监测工具gotop
- Linux下使用 ./ 来运行可执行文件
- uva 10158	War
- 使用selenium自动爬取斗鱼直播平台的所有房间信息
- 性能测试指南 | 一些实用的排查命令(未完待续)
- Linux下的硬盘信息查看
热门文章
- [开源]Qt宝马车标
- sunpinyin uint.h
- 解决vue项目404 nginx url转发
- 动动嘴就可以解锁?来看下华为最新的技术专利
- 5000配置一台游戏型计算机,开学装机:2020年如何配一台5000元主流配置的游戏主机?...
- (摘录)英语学习方法
- 新华网:软件工程师职业前景、薪水报酬及地位分析
- 分享2个VMware等虚拟机用的Macos系统镜像11.5、12.3.1
- 魅蓝e android版本,魅蓝E的安卓7.0来了!魅蓝E2却看哭了
- 软件项目经理的职责和技能有哪些