当前位置: 首页 > news >正文

B站自研的第二代视频连麦系统(上)

导读 

本系列文章将从客户端、服务器以及音视频编码优化三个层面,介绍如何基于WebRTC构建视频连麦系统。希望通过这一系列的讲解,帮助开发者更全面地了解 WebRTC 的核心技术与实践应用。

背景

在文章《B站在实时音视频技术领域的探索与实践》中,提到了直播行业从传统娱乐直播发展到教育、电商等新形式,用户对实时互动直播的需求增加。B站基于WebRTC的开发了一套视频连麦系统:这套系统优先选择UDP协议以保证低延迟,必要时降级为TCP;且使用前向纠错和后向纠错结合解决丢包问题;并根据网络状况动态调整音视频码率和发送速率,确保实时性和画质。

但是这套视频连麦系统是提取了WebRTC的部分模块组合而成的,对于上游代码仓库的后续升级有较高维护成本,且与使用高层级抽象接口的Web浏览器端无法很好兼容互通。所以在使用了一段时间后,我们决定对其进行重构,改为使用WebRTC的标准应用编程接口(API)进行开发。

本文为上篇,将会着重介绍终端上如何使用WebRTC的标准应用编程接口来接入视频连麦业务。

信令和直接连接

WebRTC的握手主要通过“信令交换”来完成。“信令”是一个相对抽象的术语,在实际操作中,可以用一个简化的例子来解释。我现在有两个主播需要进行视频连麦,一方主播已经准备好了摄像头画面、压缩摄像头画面的编码器、麦克风音频、压缩麦克风音频的编码器,以及用于数据传输的协议和网络地址。进行视频连麦的另一方主播,需要相应地准备好可以接收数据的网络地址、可以解析传输协议的解析器、以及用于解码这些音视频数据的解码器。

因此,发送端的主播需要告诉接收端的主播自己即将开启的视频和音频分别使用了哪种编码格式,并通过哪个IP地址和端口进行数据发送。同时,接收端的主播也需要告知发送端的主播自己可以接收音频和视频,并通过什么IP地址和端口接收数据。双方在交换了这些信息后,发送端的主播就可以将数据发送到接收端的主播的IP地址。通过这一过程,双方可以互相接收对方的声音和画面,从而实现视频连麦。

上述流程虽然理想,但实际操作中可能面临一些挑战。例如,接收端的主播无法解析发送端的数据或解码其音视频,这种情况该如何处理?为了尽量减少这种问题的发生,实际使用中,发送端通常会一次性列出多种不同格式的编码。接收端则从中选择其能识别的格式并通知发送端。发送端随后仅使用双方兼容的编码格式进行传输。同样,对于传输协议,假如发送端能传输前向纠错的数据包以改善高延迟网络下的通信质量,但接收端无法识别这些数据包,那么传输这些数据包反而会占用网络资源。

在WebRTC中,“信令”是一种用于记录和传输会话描述协议(Session Description Protocol, SDP)的机制。SDP最终表现为一个包含编码格式、传输协议、IP地址、端口及一些附加信息的长字符串。

当需要建立WebRTC通信连接时,两个用户会互相传递这样一个字符串。一个用户将该字符串发送给另一个用户,接收方随后也会返回一个类似的字符串。通过这个过程,双方就能互相了解使用什么格式和协议,通过哪个IP地址和端口进行数据传输,从而实现通信连接。

在此过程中,想要变更传输细节的一方会发送一个称为Offer的SDP字符串,另一方在解析Offer后修改本地状态,随后生成Answer并传回。这种来回交换信令的过程被称为协商(negotiate)。理解这一过程时,可以将其类比为一次双方状态同步的远程过程调用,这或许会更容易理解。

以实际例子为例,用户A的SDP字符串中详细列出了以下信息:音视频的收发地址为10.0.0.2,端口为17723;传输协议使用SRTP,视频的编码和解码均使用H.264,SSRC为114514(由于音视频共用一个端口进行收发,因此需要一个“编号”来区分发出的数据包是音频还是视频,这个编号就是SSRC),音频的编码和解码均使用OPUS。用户B收到该SDP字符串后,会解析其中的内容,从而知道往10.0.0.2的17723端口发送何种数据,确保用户A能正常处理。然后,用户B也会回传一个类似的SDP字符串,包含上述信息。用户A同样会解析该字符串,确保后续发送的音视频数据能够被用户B接收并正常处理。

以伪代码表示,由一台服务器在两个用户之间中转数据,流程大致如下:

用户A {pc = 创建RTCPeerConnection对象给pc添加视频收发器(Transceiver)用于发送或接收给pc添加音频收发器(Transceiver)用于发送或接收offer = await pc.CreateOffer() // offer里包含了IP地址、端口和收发器能使用的协议、编码等信息await pc.SetLocalDescription(offer)等待IP地址、端口等信息(即:Candidate)获取完成offer = pc.GetLocalDescription()通过服务器中转将offer发送给B
}
用户B {offer = 收到Offerpc = 创建RTCPeerConnection对象监听pc的创建新收发器的事件await pc.SetRemoteDescription(offer)answer = await pc.CreateAnswer()await pc.SetLocalDescription(answer)等待IP地址、端口等信息获取完成answer = pc.GetLocalDescrption()通过服务器中转将answer发送给Apc.等待连接成功的事件
}
用户A {answer := // 收到answerpc.SetRemoteDescription(answer)pc.等待连接成功的事件
}

在收到连接成功的事件之后,就可以通过收发器的接口和回调发送和接收音视频数据了。

因为WebRTC是一种比较成熟的技术,相关的示例资料在网上也好找,能解释这个字符串里哪些代表什么意思,但篇幅特别长,这里就不赘述了。

选择性转发服务器

在业务玩法逐渐变得复杂之后,这种用户之间的连接形式就应付不过来了。经常看直播的小伙伴都知道,网络直播的视频连麦会出现人传人的现象:一开始是两个人,然后变成三个,四个...九个,越来越多。如果连麦是用户之间直接连的,假设主播甲乙丙丁在视频连麦,主播甲就要把自己的音视频数据发给乙丙丁发三遍啊三遍,而且乙丙丁也逃不掉也得这么发。现在中国的家用宽带大部分是上传远小于下载的,结果就是人一多就可能又卡又糊了。

鉴于是同样的数据发这么多遍,如果有一台服务器能帮我把这个数据发给需要接收的人,那么我自己就只要发一遍给服务器就够了。所以B站就设计了这样的服务器来帮用户转发数据,这样主播就只要发一份给服务器,服务器发给另外三个人,这样正好适配了前面说中国的家用宽带大部分是下载远大于上传的特点。

服务器也运行一套WebRTC的模块,这样客户端连人和连服务器就没什么区别,也是通过交换SDP。所以服务器照常收offer、给客户端回answer,客户端就能和服务器连上,不需要区分对面是普通人还是服务器。服务器用这种方式和所有在同一个“房间”里连麦的人建立了连接;这个“房间”内的用户只和服务器连接,服务器在这些人之间有选择性地转发数据(例如,用户A只请求B和C的数据,那么A的数据不会被发回来,D的数据也不会发回给A),通过这种方式就可以实现多人连麦了。

关于选择性转发服务器的细节,将会在单独的一篇详细剖析。

信令状态

在由用户之间直接连接变成只与服务器连接之后,会出现单个RTCPeerConnection实例中,使用多个媒体收发器来接收来自不同视频连麦对手的数据的需求。考虑到不同的视频连麦对手使用的编码器可能有不同(举个例子,电脑性能好的用户可以使用AV1编码来发送视频数据,而电脑性能一般的用户只能使用H.264来发送视频数据),并且在一个视频连麦的“房间”内,参与的主播又是可以随时进出房间,所以不同媒体收发器需要协商不同的编解码设置,且媒体收发器要动态增加和删除。在上一节的伪代码中演示了如何在两个用户间创建连接,伪代码中完成了所有媒体收发器的创建然后才开始使用SDP进行协商,并没有涉及连接建立之后再添加或者删除收发器的操作。

这边我们引入一个新的概念:信令状态。在上面的例子中,对LocalDescription、RemoteDescription进行Set操作之后,信令状态就会改变。信令状态只能遵循一定的顺序变化。一个最简单的典型流程是:

图片

在这个信令交换的流程里面,需要重点观察stable, have-local-offer, have-remote-offer三个状态,这个状态的变化,通过RTCPeerConnection上的signalingstatechange事件可以监听变化;通过signalingState属性可以获取状态;遵循以上流程的话,webrtc就不会老报错。同时negotiationneeded事件指明了是不是需要进行信令交换,需要的时候事件会触发……这么说感觉很难懂,套个例子好理解点。

如果将状态机和事件引入上述用户直接建连的例子中。用户A创建了RTCPeerConnection对象,然后在对象上添加音频和视频收发器。注意,此时negotiationneeded事件会触发,意味着如果想要连接对手知道你创建了收发器,需要和它进行一次SDP交换。于是,A这里调用createOffer方法,生成己方的Offer SDP,并使用setLocalDescription更新本地的会话描述;此时,RTCPeerConnection的信令状态会变为have-local-offer。然后,A的Offer SDP通过网络传输到连接对手B那边,B也创建RTCPeerConnection,然后将A的Offer通过setRemoteDescription设置进去,此时B的RTCPeerConnection信令状态会变为have-remote-offer。B调用createAnswer生成己方的Answer SDP,并使用setLocalDescription更新本地的会话描述,此时B的信令状态变成stable。B的Answer SDP发送到A那边,A使用setRemoteDescription更新远程描述(即,连接对手的描述),A的状态也变为stable。这样一次添加收发器的流程就完成了,并且两方收发器的状态同步。

于是我们现在了解了引入negotiationneeded事件和signalingState属性之后,动态修改媒体收发器的事情就变得简单了。在连接已经建立之后,如果一方添加、删除或者修改媒体收发器,negotiationneeded事件会再次触发,此时再进行一次上述SDP交换流程,连麦双方的状态就能重新同步。

数据通道

从上面的流程中可以看出,WebRTC和网络直播中常用的RTMP、HTTP协议有很大的不同。使用RTMP推流的时候,是建立一个TCP连接,完成RTMP协议握手,然后指定数据传输的“媒体流名称”等信息,最后实时发送音视频数据流;而通过HTTP传输直播流的方式,是建立一个TCP连接,然后通过HTTP动词“GET”指定需要拉取的媒体流名称,然后服务器返回一个HTTP状态码,并持续发送音视频数据流。如果类比上述两种方式,那么WebRTC对于开发人员来说将是这样的:双方通过IP地址和端口等信息建立WebRTC连接,A添加媒体收发器后B马上收到事件回调,B这边回调函数执行完之后A收到操作完成的信息。但实际上A和B建立连接之后,只能收发媒体流;这些媒体收发器的控制信息,通过额外的SDP交换来完成,只要双方没有经过这种手动交换SDP的过程,那么一方修改了媒体收发器的状态,WebRTC内部不会给你进行远程过程调用(Remote Procedure Call)啥的,你不手动做SDP交换另一方就不会知道。

为了方便进行这种SDP交换流程,WebRTC在媒体收发器之外还提供了“数据收发器”——数据通道(Data channel)。数据通道可以传输非音视频音视频数据,内部不会像媒体收发器那样进行音视频的编码和解码,而是原原本本把调用发送函数时候传入的数据发给另一端。这样,在第一次进行SDP交换建立连接的时候,可以只创建一个数据通道完成建连,后续再添加、删除、修改媒体收发器的时候,就通过数据通道来传输SDP字符串,不再需要准备额外的渠道来收发SDP完成协商。

业务动作

对于实际在线上使用的视频连麦来说,需要一些远程过程调用来完成业务动作,这些远程过程调用的请求也会使用数据通道进行传输。以最基础的必要动作为例,视频连麦是需要区分连麦房间的,主播ABCD在进行连麦的同时,主播EFGH也可以进行视频连麦,而且ABCD和EFGH不会互相看见对方。所以需要有一个远程过程调用来告诉服务器,当前的视频连麦会话是属于哪一个房间的。在音视频传输方面,也分为“我要发送音视频”和“我要接收某某人的音视频”这样的操作。所以需要自己设计一套协议,表明这个数据是请求还是响应或者是事件通知之类,具体是哪个远程过程调用方法,携带什么参数。然后把数据结构以protobuf、messagepack、json等形式序列化之后通过数据通道发送。

总结

下面开始视频连麦的技术总结。

B站自研的第二代视频连麦系统使用标准WebRTC接口,初始状态下使用专门的接口获取服务器的信息,基于服务器信息创建只有一个数据通道的SDP完成信令协商,与服务器建立连接;

视频连麦过程中只与服务器建立连接、不与连麦对手直接建立连接,通过服务器在不同参与者之间转发音视频数据;

通过数据通道来回传输远程过程调用的请求和响应,包括加入房间、发布和接收音视频流的请求、执行房间管理操作等;涉及到音视频变更的,请求和响应需要携带SDP字符串。

通过这种方式,视频连麦能力可以使用同样的逻辑流程运行于web端、android端、iOS端、Windows端,不会像第一代那样受限于web端无法调用内部模块而无法在网页上运行。

预告

基于webrtc在客户端完成了包括连接建立和视频连麦业务需要的音视频发布、订阅等操作后,后续将介绍选择性转发服务器如何接受这种形式的连接,响应发布订阅请求,并完成包括数据转发、录像留存、业务方远程过程调用接口等后端功能。

-End-

作者丨雷鸣、大熊哥

相关文章:

B站自研的第二代视频连麦系统(上)

导读 本系列文章将从客户端、服务器以及音视频编码优化三个层面,介绍如何基于WebRTC构建视频连麦系统。希望通过这一系列的讲解,帮助开发者更全面地了解 WebRTC 的核心技术与实践应用。 背景 在文章《B站在实时音视频技术领域的探索与实践》中&#xff…...

【远程控制】安装虚拟显示器

todesk远程发现没显示器的机器有问题 电脑如果不外接一个显示器那么会默认为1024 768 分辨率需要安装虚拟显示器参考 竟然是一个隐私屏幕的解决方案。 虚拟显示器 Parsec-vdd 项目地址 Parsec-vdd 最大的优点是:支持 4K 高刷、可添加多个虚拟屏、 H-Cursor&#…...

基于HAI部署DeepSeekR1的招标文书智能辅助生产开发与应用

一、前言 1.1行业背景 在日常商业活动中,招投标流程往往是企业竞标和项目落地的关键一环。其中,招标文书的编写工作对于投标企业极具挑战:既要保证逻辑清晰、条理分明,又必须遵循招标机构的各类格式规范,甚至还有特定…...

解决whisper 本地运行时GPU 利用率不高的问题

我在windows 环境下本地运行whisper 模型,使用的是nivdia RTX4070 显卡,结果发现GPU 的利用率只有2% 。使用 import torch print(torch.cuda.is_available()) 返回TRUE。表示我的cuda 是可用的。 最后在github 的下列网页上找到了问题 极低的 GPU 利…...

模拟实战-用CompletableFuture优化远程RPC调用

实战场景 这是广州某500-900人互联网厂的面试原题 手写并发优化解决思路 我们要调用对方的RPC接口,我们的RPC接口每调用一次对方都会阻塞50ms 但是我们的业务要批量调用RPC,例如我们要批量调用1k次,我们不可能在for循环里面写1k次远程调用…...

深入解析:Jsoup 库的多功能应用场景

Jsoup 是一个强大的 Java 库,主要用于解析和操作 HTML 文档。它不仅广泛应用于网络爬虫和数据抓取,还在网页内容分析、数据清洗与处理、自动化测试等多个领域有着广泛的应用。本文将详细介绍 Jsoup 库的多种用途,并提供具体的代码示例。 一、…...

Polardb三节点集群部署安装--附虚拟机

1. 架构 PolarDB-X 采用 Shared-nothing 与存储计算分离架构进行设计,系统由4个核心组件组成。 计算节点(CN, Compute Node) 计算节点是系统的入口,采用无状态设计,包括 SQL 解析器、优化器、执行器等模块。负责数据…...

Redis - 全局ID生成器 RedisIdWorker

文章目录 Redis - 全局ID生成器 RedisIdWorker一、引言二、实现原理三、代码实现代码说明 四、使用示例示例说明 五、总结 Redis - 全局ID生成器 RedisIdWorker 一、引言 在分布式系统中,生成全局唯一ID是一个常见的需求。传统的自增ID生成方式在分布式环境下容易出…...

【Vitest】单元测试

文章目录 测试:Vitest一、安装二、断言三、回调测试四、对象方法五、模拟第三库 测试:Vitest 一、安装 npm install vitest创建文件:example.test.ts 运行测试: npx vitest example二、断言 import { expect, test } from vi…...

达梦数据库从单主模式转换为主备模式

目录标题 达梦数据库单主转主备配置笔记前期准备服务器环境数据库安装磁盘空间 流程流程图说明基于脱机备份方式的单实例转主备流程图详细步骤说明 详细步骤1. 检查主库归档模式2. 配置主库配置文件dm.ini 文件dmmal.ini 文件dmarch.ini 文件 3. 备份主库数据库4. 备库配置新建…...

【Elasticsearch】nested聚合

在 Elasticsearch 中,嵌套聚合(nestedaggregation)的语法形式用于对嵌套字段(nestedfields)进行聚合操作。嵌套字段是 Elasticsearch 中的一种特殊字段类型,用于存储数组中的对象,这些对象需要独…...

虹科波形小课堂 | 三分钟掌握车辆相对压缩测试!不拆发动机、不测缸压就能判断故障缸!

不拆发动机、不测缸压,只测个电流也能知道哪个缸压缩有问题?没错!做个相对压缩测试,测下起动电流就行,简单又实用!今天,从原理到方法,几分钟教会你! 我们都知道&#xf…...

【玩转全栈】--创建一个自己的vue项目

目录 vue介绍 创建vue项目 vue页面介绍 element-plus组件库 启动项目 vue介绍 Vue.js 是一款轻量级、易于上手的前端 JavaScript 框架,旨在简化用户界面的开发。它采用了响应式数据绑定和组件化的设计理念,使得开发者可以通过声明式的方式轻松管理数据和…...

基于 Spring Cloud + Spring AI + VUE 的知识助理平台介绍以及问题

前言(一些废话) 在看这篇文章的各位大佬,感谢你们留出几分钟时间,来看这个产品介绍,其实重点说实话,不是这个产品怎么样。而是在最后有一个郁结在心里的几个问题,希望大佬们能给出一些建议。万…...

< 自用文儿 > 下载 MaxMind GeoIP Databases 对攻击的 IP 做 地理分析

起因 两个 VPM/VPS,安装了 fail2ban 去拦截密码穷举攻击。每天的记录都在增长,以前复制屏幕输出就行,一屏的内容还容易粘贴出来的。昨天已经过 500 条,好奇 fail2ban 是如何存储这些内容的?就发现它在使用 SQLite3 数…...

前端知识速记:重绘和回流

前端知识速记:重绘和回流 一、什么是重绘与回流 1. 重绘(Repaint) 重绘是指当元素的外观发生变化时,浏览器需要重新绘制这些元素。由于这些操作不会改变元素占据的空间,因此不需要进行回流。常见的重绘操作包括&…...

webrtc peerconnection_client peerconnection_server 连接失败问题解决 win10 win11

0 常见问题 (1) webrtc peerconnection_client 连接 peerconnection_server 无连接列表 (2)连接导致崩溃debug状态下因为这个断言 RTC_DCHECK_RUN_ON(&capture_checker_); 1 在 peerconnection\client\main.cc 当中 定义类 class CustomSock…...

【C++】STL——list的使用与底层实现

目录 💕1.带头双向链表List 💕2.list用法介绍 💕3.list的初始化 💕4.size函数与resize函数 💕5.empty函数 💕6.front函数与back函数 💕7.push_front,push_back,pop_front,pop_back函数…...

iOS 音频录制、播放与格式转换

iOS 音频录制、播放与格式转换:基于 AVFoundation 和 FFmpegKit 的实现 在 iOS 开发中,音频处理是一个非常常见的需求,比如录音、播放音频、音频格式转换等。本文将详细解读一段基于 AVFoundation 和 FFmpegKit 的代码,展示如何实现音频录制、播放以及 PCM 和 AAC 格式之间…...

【PyTorch】解决Boolean value of Tensor with more than one value is ambiguous报错

理解并避免 PyTorch 中的 “Boolean value of Tensor with more than one value is ambiguous” 错误 在深度学习和数据科学领域,PyTorch 是一个强大的工具,它允许我们以直观和灵活的方式处理张量(Tensor)。然而,即使…...

《Brief Bioinform》: 鼠脑单细胞与Stereo-seq数据整合算法评估

一、写在前面 基因捕获效率、分辨率一直是空间转录组细胞类型识别的拦路虎,许多算法能够整合单细胞(single-cell, sc)或单细胞核(single-nuclear, sn)数据与空间转录组数据,从而帮助空转数据的细胞类型注释。此前我们介绍过近年新出炉的Stereo-seq平台&…...

Readest(电子书阅读器) v0.9.53

Readest 是一款开源电子书阅读器,专为沉浸式和深度阅读体验而设计。它是对Foliate的现代重写,利用Next. js 15和Tauri v2在macOS、Windows、Linux和Web上提供无缝的跨平台体验,并即将支持移动平台。 软件特色 多格式支持 支持EPUB、MOBI、K…...

一个完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (二)

📄 本地 Windows 部署 Logstash 连接本地 Elasticsearch 指南 ✅ 目标 在本地 Windows 上安装并运行 Logstash配置 Logstash 将数据发送至本地 Elasticsearch测试数据采集与 ES 存储流程 🧰 前提条件 软件版本要求安装说明Java17Oracle JDK 下载 或 O…...

青少年编程与数学 01-011 系统软件简介 04 Linux操作系统

青少年编程与数学 01-011 系统软件简介 04 Linux操作系统 一、Linux 的发展历程(一)起源(二)早期发展(三)成熟与普及(四)移动与嵌入式领域的拓展 二、Linux 的内核与架构&#xff08…...

《PMBOK® 指南》第八版草案重大变革:6 大原则重构项目管理体系

项目管理领域的权威指南迎来关键升级!PMI 最新发布的《PMBOK 指南》第八版草案引发行业广泛关注,此次修订首次将项目管理原则浓缩为 6 大黄金法则,重构 7 大绩效域,并首度公开过程组与绩效域的映射关系。本文将全面解析新版核心变…...

【云安全】以Aliyun为例聊云厂商服务常见利用手段

目录 OSS-bucket_policy_readable OSS-object_public_access OSS-bucket_object_traversal OSS-Special Bucket Policy OSS-unrestricted_file_upload OSS-object_acl_writable ECS-SSRF 云攻防场景下对云厂商服务的利用大同小异,下面以阿里云为例 其他如腾…...

阿里140 补环境日志

所有属性值是 __cheng________ 都是我做的防止套代理 非140环境检测代码 这个日志绝大多数 是做和浏览器tostring结果 处理一致 方法: toString 函数: ...... 结果: ..... 当前代码补了事件和dom 实际手补 比这少些 下方为环境日志: VM526 vm.js:…...

OpenVINO环境配置--OpenVINO安装

TOC环境配置–OpenVINO安装 本节内容 OpenVINO 支持的安装方式有很多种,每一种操作系统以及语言都有对应的安装方法,在官网上有很详细的教程:   我们可以根据自己的需要,来点选环境配置和安装方法,然后网页会给出正…...

ES6——对象扩展之Set对象

在ES6(ECMAScript 2015)中,Set 对象允许存储任何类型的唯一值,无论是原始值还是对象引用。Set 对象有一些有用的方法,可以操作集合中的数据。以下是一些常用的 Set 对象方法: 方法描述 add 向 Set 对象添加…...

C++学习-入门到精通【14】标准库算法

C学习-入门到精通【14】标准库算法 目录 C学习-入门到精通【14】标准库算法一、对迭代器的最低要求迭代器无效 二、算法1.fill、fill_n、generate和generate_n2.equal、mismatch和lexicographical_compare3.remove、remove_if、remove_copy和remove_copy_if4.replace、replace_…...