一文解析WebRTC视频数据流程

网友投稿 606 2022-11-14

一文解析WebRTC视频数据流程

本次分享的主题是WebRTC视频流数据流程分析,主要内容可以分为以下几个部分:

WebRTC 代码库简介

分析方法

实战:客户端视频录制

01 PART WebRTC 代码库简介 1.1 WebRTC简单介绍

关于什么是WebRTC,如何用一两句话简单说明? WebRTC是一个Web端 RTC的互联网标准,同时我们也会用WebRTC来指代一个开源项目,是目前完成度最高、最流行的RTC框架,是由Google开源的项目。 1.2 WebRTC版本说明

上图是我在之前参与过的一个项目中截取的,是在Sourcetree中截取的。我们可以看到其分支非常复杂,但其实这是遵循GitFlow的版本控制模式运行所导致的结果,Gitflow被很多人诟病的地方就是提交的版本记录分支结构非常复杂,难以追溯历史。 1.3 WebRTC代码目录

对于个人来说如何尽快上手大型项目?例如WebRTC或者其它的开源项目像FFmpeg、GStreamer等。包括大家入职新公司,很可能会接手或参与到较大的项目中,虽然可能不都会像WebRTC那么巨大,但还是存在一定的挑战性。在这里分享一些我的经验,希望能为大家提供些帮助。 首先,第一步就是“跑起来”,只有把相关项目的demo运行起来,以此才能对项目有更加直观的了解,了解其相关功能,以功能实现的位置作为切入点,思考其实现方式、方法。 第二步,“从外部的API入手,顺藤摸瓜”。例如下图是iOS的代码,首先找到外部的API,如代码中RTCCameraVideoCapture是用来实现相机采集的,然后就可以看类中是如何调用接口和处理数据的。

第三步“基于基础知识(音频采播系统接口),搜索定位关键函数/类”,第二步例如在Android或者iOS下我们是先找到实现相应功能所需要调用的外部接口,可以根据这些关键的接口在代码中进行搜索发现关键的函数和类。但不是所有的逻辑都会有外部Web接口,例如WebRTC中音频相关的实现就是不需要调用任何接口的。下图是一个iOS的例子,对于音频播放最关键的函数是AudioOutputUnitStart,即开启一个Audio Unit。我们在搜索后可以找到voice_processing_audio_unit.m文件,其中包含的一个Start函数,我们就可以进一步观察函数以及头文件有哪些接口,例如初始化start、stop等,音频就可以从这里进行外扩或阅读源码。

第四步“静态阅读源码+单步调试”。静态阅读源码主要是利用IDE的代码跳转,但是gn其生成exXcode工程文件的方式有一些特殊,很多代码跳转会跳不过去或者跳转到错误地方。所以更多的时候我还是使用全局搜代码,尽管效率稍低,但目前没有其它更合适的办法。单步调试,在代码中的某些位置,我们希望了解其下一步是如何跳转的,而代码无法直接跳转,搜索的结果也并不知道是什么作用无法准确判断,这时我们可以通过加断点进行验证。

如图所示,是视频编码相关的一个类的函数,在加入断点后,我们可以观察到视频数据是如何从系统的回调接口到采集RTCCameraVideoCapture的类再一步步到编码的类,非常清晰。 在软件开发中,没有银弹,都是那些看似朴实无华但往往非常有效的办法,掌握这些方法后,再上手一些新的项目就会有一些帮助。 03 PART 视频流程介绍

完整视频数据流程(调用栈) 图中详细的列出了视频数据的整体采集、处理、传输相关步骤。简单来看,就是从上到下到最底部网络层,再由下到上最终到渲染的整体流程。所有平台的视频数据流程基本上都是大同小异的,区别只在于采集、编解码和渲染的实现不同,其余的流程基本是一致的。 采集:

视频数据经过AdaptedVideoTrackSource层之后,就可以通过broadcaster_进行分发。在安卓或者linux中可能会有多个分支,一个预览一个编码,这里我们以编码为主干进行分析。Sink实际上就是数据的消费者,通过VideoStreamEncoder来实现编码,但其只是概念上的编码,最终实际编码还是调用系统相关的类,因此最终会回到ObjectiveC层,通过一些调用到达RTCVideoEncoderH.264,再调用VideotoolbBox接口,实现H.264的硬件编码。编码完成之后会实现系统的回调,再将编码后的数据交回给C++层,即VideoStreamEncoder的OnEncodedImage回调函数中,表示一帧视频数据已经完成编码。 发送:

VideoSendStream表示要发送的视频流,通过rtp_vVideo_sSender_进行RTP打包处理,再接下来就是需要进行的RTP封包和网络传输。假设通过网络传输数据已经到达RtpVideoStreamReceiver,我们可以看到左右两边的sender和receiver在类以及函数的命名上会有一些对称的地方。RtpVideoStreamReceiver接收到RTP,并且已经完成解包以及其它的网络乱序、错误重传等处理,获得一帧完整可解码的帧,然后就会调用解码回调,送到VideoReceiveStream中进行解码操作,在这里会调用vVideo_rReceiver_的Decode函数。 解码:

vVideo_rReceiver_的Decode函数其实也是概念上的解码,叫VCMGenericDecoder,最终也会调到平台相关的ObjectiveC实现的视频硬解,即RTCVideoDecoderH.264,也是调用VideoToolBox进行解码,解码后通过DecodedFrameCallback交还给C++这一层。

渲染: 获取到视频数据后,会通过Broadcaster将数据交给sink,sink在iOS上具体是通过RTCMTLVideoView对数据进行渲染,MTL是调用iOS的Metal接口进行视频渲染。 其实图中只是视频流程中调用栈的总结,书中有一章节的内容总结了视频数据流程的更多示例代码的分析以及讲解。 04 PART 实战:客户端视频录制

首先要明确需求:1. 推流和收流都需要,即发送的数据需要录制成文件并且接收到的内容也要录制成文件;2. 其次是不希望做额外的编码,因为通常接收或者发送的视频都是已经处理(编码)好的,额外的编码会造成资源浪费。3.在不需要额外编码的情况下,我们只需要调用FFmpeg把编码后的数据存储到文件内即可。4.我们应该从哪里拿数据?

要回答从哪里拿数据这个问题,首先需要对视频数据流程有一定了解,也就是前面第三部分所介绍的内容。如上图红框所示,VideoSendStreamlmpl::OnEncodedimage中已经接收了编码后的视频数据,但其数据存在形式还是完整一帧,并没有拆分成一个一个的RTP包。接收端情况比较复杂,在网络传输时会出现乱序到达、丢包缺失等问题,造成网络数据的不可用。因此,我们需要找到一个已经对上述问题进行过处理的数据点,即解码之前的数据点,VidioReceiveStream的HandleEncodedFrame函数中。 当我们找到数据接入点后,需要进行的操作就是修改代码,增加API,实现相关功能。如在Android和iOS上希望有Java或Object C的接口暴露出来供APP层调用。想要修改iOS接口,就需要修改SDK目录下的代码。

举例如图所示 ,我们需要修改RTCPeerConnection文件,其中所定义为WebRTC的主类。增加Start/StopRecoder的接口,通过dir的参数表明想要录制视频的方向(发送或者接收)。

SDK仅为C++接口的boinunding,因此还需要修改API目录里面的C++接口,即修改peer_connection_interface.h,为C++的PC类增加接口。

API里面只是程序接口,我们需要修改程序的实现类,实现类主要在pc中,但这里有一点特殊的是,业务流程和实现逻辑,call中也是很重要的一部分。 如图所示,它是对api/peer_connection_interface的一个子类,一个具体集成的实现类,我们为其增加接口,但是在这里我们不在peer_connection_interface的类中调用录制相关的代码,而是在call里进行修改。

从call到VideoSendStream有如上图所示的文件需要修改,Call里面有一个Stream接口的定义,然后在video目录下会有call里面定义的Stream接口的子类、实现类,video_send_stream_impl,在OndecodedImage中,将完整的一帧给recorder,再调用FFmpeg的头文件接口即可。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:谁说中国没有混合云服务
下一篇:电信运营商的云机遇-【软件和信息服务】2015.01
相关文章

 发表评论

暂时没有评论,来抢沙发吧~