草庐IT

webrtc入门:4.RTCPeerConnection连接音视频流

go2coding 2023-10-09 原文

有了前面的准备,离视频会议的建设又进了一层了。我们现在准备好了音视频流的数据。双方的视频数据需要交给对方,对方就能看到相关的数据,达到了视频会议的效果。

假设我们是一个视频会议的发起人,我们当然先要知道,我们想跟谁进行视频通话,对方需要把相关的环境数据,比如我用的是什么视频编码啊,我们通信的协议是什么?我们把这些数据信息取了个名字叫 sdp。互相交换了环境数据后,被叫端需要把数据的地址准备好,这些数据协议我们成为 ice,当数据准备完成以后,被叫端把ice发给发起端,发起端通过这个ice就能够连上被叫端了。

简单的总结,互换两种信息,环境描述数据和数据地址。这两种叫为 sdpice。下面的例子为了简单起见,在同一个程序中,同时设置了两个角色,发起端和被叫端。这样我们比较容易看起 webrtcRTCPeerConnection的原理。

  • 1创建摄像头数据

这个在前面的章节中,都有介绍,也就是我们音视频数据的基础。

//开启音视频源
async function start() {
  console.log('Requesting local stream');
  
  try {
	  //捕获摄像头和麦克风的流,放到localVideo中
    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
    console.log('Received local stream');
    localVideo.srcObject = stream;
    localStream = stream;
    callButton.disabled = false;
  } catch (e) {
    alert(`getUserMedia() error: ${e.name}`);
  }
}

2.创建RTCPeerConnection通道,让两端互换信息

RTCPeerConnection 就是webrtc的核心,他创建了通道,互相设置彼此的信息,生成自己的直播地址。

pc2拨打pc1pc1把自己的sdp给了pc2pc2做了相应,在把自己的sdp给了对方。双方的信息交互完成。这时候pc1生成了自己的播放地址 也就是iceice当然也是需要给对方,对方用ice就能够看见被叫端的数据了。

//拨打,建立连接
async function call() {
  callButton.disabled = true;
  console.log('Starting call');
  startTime = window.performance.now();
  
  const configuration = {};
  console.log('RTCPeerConnection configuration:', configuration);
  
  //源连接,
  pc1 = new RTCPeerConnection(configuration);
  //当ice准备好后,加到目标源中
  pc1.addEventListener('icecandidate', e => onIceCandidate(pc2, e));
  //把localStream的音视频,放到源中
  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
  
  //目标
  pc2 = new RTCPeerConnection(configuration);
  //等待源发来的流
  pc2.addEventListener('track', gotRemoteStream);
  
  

 

  try {
    console.log('pc1 createOffer start');
	
	const offerOptions = {
		offerToReceiveAudio: 1,
		offerToReceiveVideo: 1
	};

	//创建和设置连接描述
    const desc_pc1 = await pc1.createOffer(offerOptions);
	console.log("desc_pc1:");
	console.log(desc_pc1);
	await pc1.setLocalDescription(desc_pc1);
	
	//目标 拿到源的连接描述后,给自己,并生成自己的连接描述
	await pc2.setRemoteDescription(desc_pc1);
	const desc_pc2 = await pc2.createAnswer();
	console.log("answer desc_pc2 :");
	console.log(desc_pc2);
	await pc2.setLocalDescription(desc_pc2);
	
	//源拿到目标的连接描述后,知道有人要来连接,开启 通道
	await pc1.setRemoteDescription(desc_pc2);
	
	
  } catch (e) {
    onCreateSessionDescriptionError(e);
  }
}

等待生成ice,添加到发起方,这样视频就连通了。

async function onIceCandidate(pc, event) {
	
  try {
	  console.log(event.candidate.address);
	  
	//源发来的ice,加入到目标中
		console.log(event.candidate);
        pc.addIceCandidate(event.candidate);
        onAddIceCandidateSuccess(pc);


  } catch (e) {
    onAddIceCandidateError(pc, e);
  }
  //console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}

来看看sdpice张什么样子,这也是协议的主要部分。

sdp

"v=0
o=- 2176126363205996170 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=extmap-allow-mixed
a=msid-semantic: WMS FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:q14N
a=ice-pwd:abTQebhAIpOyGtpZpoyuzuyU
a=ice-options:trickle
a=fingerprint:sha-256 F7:16:89:90:4E:D7:7C:94:34:1C:10:5D:46:45:46:35:A3:5B:48:E4:B5:1E:E0:DE:64:F2:71:59:40:03:22:D4
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=sendrecv
a=msid:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S f7248998-5512-4bf6-8611-14ef47dedc2d
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:2949455037 cname:WiqznckmjtPMfa13
a=ssrc:2949455037 msid:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S f7248998-5512-4bf6-8611-14ef47dedc2d
a=ssrc:2949455037 mslabel:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S
a=ssrc:2949455037 label:f7248998-5512-4bf6-8611-14ef47dedc2d
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 121 125 107 108 109 124 120 123 119 35 36 41 42 114 115 116
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:q14N
a=ice-pwd:abTQebhAIpOyGtpZpoyuzuyU
a=ice-options:trickle
a=fingerprint:sha-256 F7:16:89:90:4E:D7:7C:94:34:1C:10:5D:46:45:46:35:A3:5B:48:E4:B5:1E:E0:DE:64:F2:71:59:40:03:22:D4
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S 683b59d0-b640-4f8f-9f41-93cbfc557fb5
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=9
8a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=10
8a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=124
a=rtpmap:123 H264/90000
a=rtcp-fb:123 goog-remb
a=rtcp-fb:123 transport-cc
a=rtcp-fb:123 ccm fir
a=rtcp-fb:123 nack
a=rtcp-fb:123 nack pli
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
a=rtpmap:119 rtx/90000
a=fmtp:119 apt=123
a=rtpmap:35 H264/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=fmtp:35 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:41 AV1/90000
a=rtcp-fb:41 goog-remb
a=rtcp-fb:41 transport-cc
a=rtcp-fb:41 ccm fir
a=rtcp-fb:41 nack
a=rtcp-fb:41 nack pli
a=rtpmap:42 rtx/90000
a=fmtp:42 apt=41
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
a=ssrc-group:FID 3205961662 1789602341
a=ssrc:3205961662 cname:WiqznckmjtPMfa13
a=ssrc:3205961662 msid:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S 683b59d0-b640-4f8f-9f41-93cbfc557fb5
a=ssrc:3205961662 mslabel:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S
a=ssrc:3205961662 label:683b59d0-b640-4f8f-9f41-93cbfc557fb5
a=ssrc:1789602341 cname:WiqznckmjtPMfa13
a=ssrc:1789602341 msid:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S 683b59d0-b640-4f8f-9f41-93cbfc557fb5
a=ssrc:1789602341 mslabel:FmdNsj66Elz3qRkmVom6A4WVF8VACLeDmy7S
a=ssrc:1789602341 label:683b59d0-b640-4f8f-9f41-93cbfc557fb5
"

ice

ice是不是包含着ip和端口号。

{
"candidate": "candidate:4142333030 1 udp 2122260223 192.168.40.34 56513 typ host generation 0 ufrag Ds60 network-id 1",
"sdpMid": "0",
"sdpMLineIndex": 0
}

用命令查看56513端口号:

netstat -aon|findstr "56513"

获得如下的信息:

UDP    192.168.40.34:56513    *:*                                    13900

端口号确实是开着的。

对于ice的理解,同样的看下面的代码:

  //源连接,
  pc1 = new RTCPeerConnection(configuration);

  //把localStream的音视频,放到源中
  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
  
  //目标
  pc2 = new RTCPeerConnection(configuration);
  //等待源发来的流
  pc2.addEventListener('track', gotRemoteStream);

  //当ice准备好后,加到目标源中
  pc2.addEventListener('icecandidate', e => onIceCandidate(pc1, e));

这样也是可以进行连接显示的,我们就可以这样的理解iceRTCPeerConnection 中虽然是两方,当时只有一路流,双方都生成了ice,最后ice通过协商的方式进行选取。我们这里固定了一方的ice,这样可能会减少ice的协商时间。

有关webrtc入门:4.RTCPeerConnection连接音视频流的更多相关文章

  1. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  2. ruby - 无法在 60 秒内获得稳定的 Firefox 连接 (127.0.0.1 :7055) - 2

    我使用的是Firefox版本36.0.1和Selenium-Webdrivergem版本2.45.0。我能够创建Firefox实例,但无法使用脚本继续进行进一步的操作无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055)错误。有人能帮帮我吗? 最佳答案 我遇到了同样的问题。降级到firefoxv33后一切正常。您可以找到旧版本here 关于ruby-无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055),我们在StackOverflow上找到一个类

  3. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  4. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  5. 动漫制作技巧如何制作动漫视频 - 2

    动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、

  6. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

    2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

  7. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  10. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

随机推荐