当前位置: 首页 > 科技观察

清华作业指导:一个人在课堂上擦雨需要做多少功?快手工程师详解两周搞定的方法

时间:2023-03-22 13:25:41 科技观察

昨天,清华自动化一年级学生的C++作业登上知乎榜首。可以分享屏幕、直播语音、在线答疑……其实现的难度和工作量似乎超出了大一学生的能力范围。就连清华特等奖得主、阿里P6都表示自己一个人完成不了。距离提交截止日期只剩五六周了,这个作业真的能完成吗?在昨天的讨论区,我们看到大部分的评论都是对这个作业的抱怨。但也有人提醒大家,何不试试看呢?毕竟作业还是要交的。作为雨课堂的技术支持者,快手的音视频工程师范伟给出了专业的解答。范伟拥有十多年的工作经验。春节后,深度参与雨课堂与快手项目的联合开发,为雨课堂提供音视频底层技术支持。据范伟介绍,拥有雷课堂总共需要2周的开发时间。当然,前提是“这些知识你都学会了”和“踩坑经验丰富”。你们兴奋吗?我们来看具体分析。一个人单独玩一个雷教室需要多少功夫?雨课堂是清华大学开发的一款在线教育APP,可以支持教师在线授课、分享PPT、学生与教师语音互动。从音视频的角度来看,这种形式的在线教学其实就是一个标准的视频会议场景。说起一个视频会议软件如何从零开始,需要分前端和后端。这里前端指的是终端,后端指的是媒体服务器。前端主要功能负责音视频通信,后端主要功能负责媒体流的转发。先说前端吧。前端模块包括音视频采集、预处理、编解码、收发包等功能模块。目前,webrtc是最流行的开源视频会议项目,其代码多达80万+行。如果想单手从头开始做一个视频会议终端,虽然不需要80万行代码,但还是有难度的。采集模块首先是平台的支撑。iOS、Android、Windows、Mac、Linux,不同平台提供的音视频技术是不一样的。音视频的采集和预处理需要根据不同的平台和机型进行适配。有的算法可以利用平台的能力,有的算法需要软件处理。先说采集和预处理模块。视频通过调用系统API完成摄像头数据的采集。我不会在这里详细介绍。感兴趣的同学可以自行查阅官方文档。视频预处理包括各种美颜算法,这里略过,因为这个功能不是教育场景才需要的。这里先说说音频预处理。在实时语音通话中,直接从麦克风采集的音频包括说话者的声音和对方的声音。这是因为本地扬声器播放的对方声音也会被麦克风采集。如果不经任何处理就发送,对方会听到自己从扬声器中讲话。这种情况称为声学回声(Echo)。为了获得更自然的通话效果,需要对麦克风采集的音频数据进行处理,剔除对方说话的部分,只保留本地语音。这个过程称为回声消除(AEC,AcousticEchoCancellation)。AEC算法最常用的方法是利用自适应滤波器产生模拟回声,然后从麦克风采集的信号中消除模拟回声,达到回声消除的目的。单线AEC架构在实际项目中,会对信号进行NLP(非线性滤波),消除残留回声。同时会进行声学降噪和自动增益,提升信噪比,统称为3A(AEC/AGC/ANS)。目前几乎所有的智能手机和Mac平台都有硬件3A算法模块,其中苹果设备调校的比较好,而安卓手机的3A效果参差不齐,通常需要通过软件来实现。最快的实现方式是使用系统提供的3A算法,这里算作1人-天。编码模块之后是编解码器模块。编解码器就是将经过预处理的音视频原始数据进行压缩,以达到网络传输的目的。由于网络资源的限制,原始音视频数据量太大,无法在网络上直接传输,必须先进行压缩。压缩分为无损压缩和有损压缩。音视频数据通常采用有损压缩的方式,可以达到非常大的压缩比。常见的视频编码算法有H.264/H.265/VP8/VP9/AV1/AVS2等,音频压缩算法有AAC/Speex/Opus/G.711/G.729等常用的视频压缩算法是H.264/H.265,而大多数音频算法使用Opus。压缩算法的细节非常复杂,大部分都是由比较成熟的开源项目实现的,比如x264、x265、ffmpeg和libopus。Apple设备还提供了一个内置的硬件视频编解码器videotoolbox,可以支持H.264/H.265实时编解码器,而Android有MediaCodec提供相同的能力。在桌面平台上,Intel和NVIDIA的很多芯片都提供了qsv和nvenc函数来实现桌面上的硬件视频编解码。有了编码器之后,还需要正确配置编码参数,才能适合实时通信场景。主要影响音视频通话体验的参数是码率。在其他参数不变的情况下,码率越高,音视频质量越好,占用的网络带宽也越大。这里为了简化实现,使用了系统提供的codec实现,算作一个人一天。经过传输模块编码后的音视频数据要小得多,可以通过网络传输。Internet网络传输协议分为TCP和UDP两种方式。TCP协议是可靠传输,保证了数据的完整性和有序性,但缺点是在公网上传输速度比较慢,延迟比较大。UDP协议是一个不可靠的协议。数据只能发送,不保证一定能到达对方。但优点是发送速度快,延迟低。因此,在实时音视频通信中,会首先使用UDP协议进行数据传输。UDP数据以数据包为单位发送,每次发送一个包,最大包大小不能超过64K字节。但是由于IP层的分片路由限制,一个UDP包的大小通常被限制在一个MTU(MaxTransmissionUnit)。以太网的MTU为1500字节,因此每个UDP数据包的大小大多限制在1K字节左右。然而,编码后的视频数据与这个尺寸相比仍然太大,视频数据在发送前需要进一步分包。由于UDP协议具有不可靠传输的特点,数据包到达的顺序是无法保证的。为了使对方收到的音视频数据的顺序与发送方一致,需要在接收方对UDP包进行排序。在视频会议中,通常采用RTP协议对分组后的数据包进行封装。每个RTP数据包都包含一个RTP头,它为每个RTP数据包分配一个序列号。序号依次递增,因此接收端可以根据收到的RTP包的序号对数据包进行排序,同时知道哪些序号的数据包还没有收到,从而向对方请求重传发送端。上面说到UDP在网络传输过程中可能会丢包。由于编码后的音视频数据需要完整接收才能正常解码,所以使用UDP协议传输的RTP包需要能够处理丢包恢复。丢包恢复有两种方法,一种是FEC(前向纠错),另一种是ARQ(自动重传)。通常,在项目中会同时使用这两种方法。FEC就是对一组RTP包进行冗余编码,产生一些冗余包。冗余包包含这组RTP包的信息。当数据包丢失时,可以利用冗余数据包中的数据来恢复GroupRTP数据包数据。常用的FEC算法有RS、卷积码、喷泉码等,FEC的优点是不会引入额外的延迟。冗余包和数据包一起发送给对端,对端尝试通过接收到的数据包和冗余包进行恢复。没有额外的交互时间,但是由于网络丢包的随机性,并不是每一个FEC包都能被利用,降低了整体的带宽利用率。ARQ正是请求丢失的RTP包,要求发送方重发。在RTP协议中,重传请求可以通过NACK实现,携带RTP请求重传序列号。发送方收到NACK请求后,会重新发送序列号对应的RTP包给对端。NACK和重传包的传输引入了额外的延迟,所以ARQ会增加音视频通信的延迟,但是带宽利用率比较高。对于较差的网络,网络的带宽不是那么高。如果发送端编码后的音视频数据超过了它发送的上行带宽,就会造成网络拥塞、丢包和卡顿。为了防止网络拥塞,发送端需要预测自己的上行网络带宽并反馈给编码器,以调整编码器的码率不超过带宽上限。该算法称为带宽估计。带宽估计是实时音视频通信中非常重要的算法,其准确性将极大地影响用户体验。带宽估计算法有多种策略,webrtc使用的是Google提出的GCC(GoogleCongestionControl)算法。整个传输模块目前还没有现成的开源项目。可以自己做,也可以参考webrtc的实现。大约需要3-5天。缓冲队列到这里,发送端模块的介绍就基本介绍完了。下面说说接收端要做的一些部分。接收方收到RTP数据包后,首先根据RTP序号进行排序,如果有丢包,则通过FEC和ARQ进行恢复重传。得到完整有序的RTP包后,将RTP包重新组合,形成编码后的音视频数据。由于网络传输的不稳定性,导致接收到的数据不统一,有可能一次接收快,另一次接收慢,造成数据接收波动。这种现象称为网络抖动(Jitter)。如果此时直接解码播放,会导致视频时快时慢,声音也会发生变化。为了平滑这种网络抖动,接收端需要有一个缓冲队列,将接收到的音视频数据放入缓冲队列,然后匀速从队列中取出,从而获得相对播放流畅的音频和视频数据。从缓冲队列中取出的数据可以被解码。解码是解压缩的过程,将发送端的压缩数据恢复为原始音视频数据,然后才能在本地播放。为了提高压缩率,一些编码算法(如H.264/H.265/Opus)在压缩时参考了连续两帧的音视频数据,导致使用此类压缩算法的编码数据正在解码。有时对引用关系存在依赖关系。只有前面的数据解码正确,后面的数据才能正确解码。但是,由于网络丢包,即使采用FEC、ARQ等丢包恢复策略,部分音视频数据仍然无法完全到达接收端。这时候如果强行解码,会出现视频模糊,声音爆裂的情况。为了解决这个问题,对于不连续的视频数据,接收端需要向发送端请求对关键帧视频数据进行编码。这个关键帧数据在解码时不会引用其他视频数据帧,可以被后续编码的视频帧引用,可以解决后续视频的引用关系问题。音频通过PLC(PacketLossConcealment)算法可以根据波形产生丢失的音频数据。这里可以参考webrtc的neteq队列来实现(是的,只有webrtc是开源的,所以同学们没有其他参考),本地播放需要3天左右,最终在本地播放解码后的音视频数据。播放过程中,音视频数据在网络上的传输速度不一定相同,因此可能会出现音视频不同步的问题。这里还需要一个音视频同步模块来控制音视频的播放速度,保证音视频对齐。对齐时,由于人耳对音频速度的变化比较敏感,所以总是调整视频的速度来对齐音频。在每一个音视频数据中,都会有一个时间戳(timestamp),这个时间戳是音视频数据采集时产生的。具有相同时间戳的音频和视频应该同时播放,以保证音频和视频的同步。因此,在播放视频帧时,需要通过比较当前播放音频的时间戳来调整视频播放速度。视频在屏幕上播放时,显示的分辨率可能与实际编码的分辨率不一致。视频分辨率代表视频的像素数,分辨率越高越清晰。现在的手机和显示器,大多都支持HDPI,通过更密集的像素获得更清晰细腻的画面。但是摄像头拍摄和编码的视频分辨率不是特别高,所以在显示的时候需要放大视频。不同的视频放大算法对清晰度的影响更大。默认的线性放大算法会导致图像模糊。使用复杂的放大算法,如bicubic、lanzcos、spline等,可以获得更清晰的画面,一些特殊的内容使用特定的算法会得到更好的视觉效果(比如在人脸部分使用softcubic可以达到美颜效果).这里如果不考虑效果,用最简单的opengl渲染视频大概需要2天。这样一个简单的实时音视频通讯的终端部分就开发好了,界面酷炫时尚,可以吃。但是这里只能实现两个人的交流,在线教学的场景是一个老师对一群学生。如何实现多人之间的实时通信?这就需要后端媒体服务器作为中继来实现。MediaBackend这里需要简单介绍一下多人实时音视频通信的网络拓扑结构。对于三人以上的实时通信,由于接收端会同时接收到多人的音视频数据,如果没有服务器进行中继,则需要采用网状拓扑结构,同时接收多个人的音视频流每个终端需要同时发送给其他终端对于所有终端,网络带宽成倍增加。为了解决这个问题,需要采用星型拓扑结构,所有终端将自己的音视频数据发送到中心服务器,由服务器转发。此服务器是视频会议的后端,兼媒体服务器。有两种方法可以实现媒体服务器。一种称为MCU模式,另一种称为SFU模式。在MCU模式下,服务器在服务器端对所有音视频数据进行解码,然后合成为新的音视频流。这个视频流由每个人的视频组成,音频和每个人的音频混合,然后重新编码为音视频,然后发送到所有终端。这样,终端播放的音视频流就是合并的音视频流。这种模式对媒体服务器的性能消耗很大,所以一台服务器无法支持很多终端。SFU模式仅用于转发RTP包,不用于解码和合并。每个终端会同时接收多人的音视频流,每组音视频流需要独立处理解码,本地混合后播放。这种模式的SFU性能消耗比较低,可以支持的并发度很高。如果不考虑高并发和架构设计,最简单的实现需要3天左右。这样一个媒体服务器部署在网络上,然后它转发所有终端的音视频数据包,从而实现多人之间的音视频通信。一共需要2周的开发时间,最简单的雷课堂就完成了。当然,这只是为了完成功能上的功课。在实际项目中,需要考虑性能和架构优化、稳定性和音视频质量的提升、服务器的高并发和快速部署、各种算法参数调优和策略优化。没有几十人的团队2-3年的精细打磨,以及专业的音视频质量检测实验室,是不可能做到行业顶尖水平的。