移动音视频SDK工程实践之数据采集和处理

本文由百度智能云音视频SDK产品技术负责人李明路在LiveVideoStack线上分享的演讲内容整理而成,内容从音视频数据角度出发,梳理了音视频SDK的发展和技术演进。详细分析数据在常见音视频模块上遇到的问题与挑战,并提出相应的解决思路和技术实践。算法

文 / 李明路编程

整理 / LiveVideoStack小程序

视频回放:https://www.livevideostack.cn/video/online-lml/缓存

本次分享的主题是移动音视频SDK开发工程实践,内容主要分为如下五个部分:安全

  • 音视频SDK的技术演进
  • 数据采集管线的设计与实现
  • 特效模块数据中间件设计和实现
  • 连麦模块数据中间件设计和实现
  • 渲染模块数据中间件设计和实现

音视频SDK的技术演进

1.1 数字化进程服务器

 

多媒体技术是一项传统技术,但同时也是在不断发展与进步的,咱们能够形象的用细胞分裂来表示,即多媒体技术内部也在不断发生分裂,而音视频SDK则是其中的一个分系。网络

如下大体列出了音视频技术演进的几个重要进展。首先,在互联网化前期多媒体技术就已经开始出现普遍的应用,得益于通信技术的发展,咱们能够借助于广播、有线电视、卫星等等,将数字信号传输给用户。框架

解码器其实在整个多媒体技术的发展当中也起到很是重要的做用,只不过当时解码器的能力与如今相比仍是有些不一样,但一样能够实现解码和容器的分离。异步

此后,随着互联网的出现,咱们能够实现基于一些IP网络协议、光纤的普遍使用,经过WiFi、蜂窝网络,传输数据到设备终端(此时终端更多的仍是指PC端)。所以产生了一些在线类的音视频服务,例如点播、在线语音等典型的音视频场景。在这些场景中,SDK更多的仍是以服务端为主。ide

2010-2015年期间,随着手机硬件的发展,终端的算力不断提高,编解码芯片也得以快速发展,再加上消费者使用习惯的变化,出现了更多碎片化的场景和产品,像直播、短视频等。此时,随着手机等移动设备的普及,移动端SDK也慢慢进入到消费级领域,逐渐发展成独立技术栈。

近年来, 随着5G、AI人工智能的发展,VR/AR、语音交互等技术也在发生着新的变化,应用场景变得更加普遍,音视频SDK已经不只仅局限于移动端,将来会出如今各类设备屏幕、产品形态当中。

1.2 移动端音视频框架

 

移动端的音视频框架与其它移动端框架相比有很大不一样。音视频框架首先要立足移动端提供的系统能力,包括系统框架和硬件能力。如图,系统框架层有iOS/Android的一些多媒体框架、硬件的编解码、硬件的处理能力如CPU、GPU、NPU的处理模块,以及一些开源图像库。

系统框架层之上是第三方框架,如FFmpeg、OpenSSL(加解密);在点播场景用的比较多的Ijkplayer/Exoplayer;用于底层通信的技术:双向的,低延时的WebRTC技术,用于单向点播的RTMP,目前比较火的SRT低延时方案;除此以外,还会有一些图像处理方面的框架如GPUImage等。

在此之上,就是一些跟场景相结合比较多的模块,这里大体列出了部分核心模块。

数据从多媒体采集模块出来,会通过一路或多路的混音混流(与实际场景相结合),而后过渡到多媒体编辑模块:当下短视频的一些能力都是经过多媒体编辑这个处理单元实现,再到后面的多媒体后处理模块:例如AR特效,以及一些比较好玩的互动能力等;

内容生产完成后,咱们会根据点播、直播或者短视频等场景的不一样,采用不一样协议进行分发。再下面就是消费侧,这里能够直接读取数据,也能够间接读取数据做为缓存加快二次读取数据的载入。获取数据后会根据不一样的封装格式,如FLV、TS或者是MP4等对数据进行解封装,再进行解码和渲染操做。

相比与其它移动端框架音视频框架最特别的地方就是管线部分,由于音视频SDK的产品与其它产品不太同样,首先须要的是实时处理,数据流是不断在各个模块之间穿梭的,如何保证各个模块间的高效传输,这里提出了一个管线的概念。

1.3 惟快不破

 

简单介绍了音视频SDK的框架,接下来介绍下目前框架遇到一些问题和挑战。目前不少业务场景,其实都在追求更多的新玩法。这里可能会有一些追求更大的分辨率,还有一些更高的刷新率,甚至是更极致的体验。可是咱们知道音视频SDK很大程度上会受制于平台的能力,因为平台具备更多的差别性,因此致使音视频SDK在发展过程中,其实遇到不少的问题。另外还涉及到模块间的数据交互,所以性能对于移动端来讲是一个最大的瓶颈。

其实在有些场景像点播或短视频,720p的分辨率已经不能知足场景的使用,甚至在一些运动场景,30fps的刷新率也已经没办法知足场景的开发,固然这些其实更多的是业务挑战。除此以外,刚才也说过整个多媒体技术的发展,编码器一直起到了举足轻重的做用,因此底层技术也在追求更高效的编码效率。

对于这些问题,其实你们都能达成一个共识,就是说速度是音视频SDK框架要解决的一个问题。但抛开现象看本质,速度其实仍是来自于数据的传输,它是一个根本前提条件,所以如何让数据更高效的传递和更高效的处理,才是移动端SDK要解决的最根本问题。

数据采集管线的设计和实现

 

这里介绍一下目前一些开源产品当中数据链的设计和方案,以两个产品为例,一个是GPUImage。相信作过移动端SDK特效的同窗应该都是耳熟能详了,由于它确实是比较好用,能够提供大量的Shader。抛开Shader的不说,咱们看一下它在数据链条的设计方式。首先GPUImage提供了一个传输数据的类Output和一个实现接收数据的Input,而后经过生产模式,生产模块像相机采集模块,以及本地相册模块,其实都是实现了Output的类,生产模块生产完毕后会交由Output进行渲染,这里它的强大之处在于Output能够与可编程的Shader进行强关联,很大程度的驱动了OpenGL的运行效率。而后将渲染后RGB的数据转换成FrameBuffer这个实体的对象,整个链条都是经过FrameBuffer进行接收,而后经过链条上的组件去实现了Input后,就能拿到Output传出的FrameBuffer,以此类推将FrameBuffer进一步的传递到下个链条当中,整个链条实际上是很是清晰和简单。

咱们再来看一下右边的FFmpeg,除了提供不少的数据链条的处理机制、数据的分包,它还能够在不进行解码的状况下进行转包,也能够把数据进行解码,作一些后处理等等,整个链条其实也是比较清晰的。

 

介绍了两种多媒体框架,接下来看一下咱们团队是怎么去考虑这些问题。实际开发中,音视频SDK框架中管线实际上是不可控的,咱们来看一下这两种方案的一些优点和问题。

首先GPUImage的优点是显而易见的,就是协议相对简单清晰,另外经过可编程Shader驱动了OpenGL,这样它的运行效率变的很是快,经过大量的Shader可让开发同窗去进一步学习这门语言,是一个优秀的图像处理 开源框架。但一样存在一些问题,首先咱们看到它提供的这些Shader其实大多数都是图像处理,而且基本上都是同步处理的。因此可能致使在使用GPUImage跟一些框架进行结合的时候,当你的某个模块涉及到耗时处理,这个时候有可能会出现某个对象被其它释放掉或怎么样,会形成整个线程的不安全。

FFmpeg的优点特别多,简单说一下它的函数指针,其实也是FFmpeg的一个最佳实践,它经过指针的方式,把数据进行一层层的拆分跟传递。可是它也一样存在一些问题,好比说FFmpeg是面向过程的,它的链路设计对于不熟悉FFmpeg的同窗来讲,可能会以为链路很是复杂,使用起来可能并非那么的驾轻就熟。

因此咱们通过多轮讨论跟思考,就但愿获得一个数据管线,它首先像GPUImage,协议比较简单,用起来比较容易上手,对开发者来讲是友好的;另外须要它是可控的,由于随着SDK的模块愈来愈多,咱们发如今不少场景下,会出现不可控的问题;除此以外咱们还但愿它是支持同步或者是异步的,调度是安全的,链路配置是简单可依赖的。

 

咱们也总结了本身的一些方法论跟实践:专一链式,这主要仍是考虑到音视频框架,链式是它的一个核心思想,因此咱们听从这种法则自研数据管线。

首先是制定数据协议,主要解决的是最基本的模块间数据的高效、稳定的传递。这里能够看到左图,它的方案跟刚才介绍的GPUImage有些类似,可是也有些不一样。咱们也提供了相似的AVOutput的模块和AVInput的数据传输接收的协议。可是咱们并无跟GPUImage的OpenGL进行绑定,只是单纯的去记录和管理这条链条上的各个的组件,咱们叫target。而后经过Dispatcher的机制,分发从生产端上报的视频帧,经过视频帧不断的传递到各个链路的target当中,而每一个target实现AVInput其中的协议方法。例如frame跟type两个函数,frame就是从raiseFrame Output传递下来的数据;type主要是作一些音视频的音频跟视频的区分。

除此以外咱们还支持一些二进制场景的分发,主要是为了配合像直播这种对数据有分发要求的场景作了一些协议的升级。在链条的最末端,能够看到咱们也实现了AVControl,跟以前不太同样是咱们制定了一个控制协议,这个控制协议主要是为了控制数据的流入和流出。为何要作这个事情呢?咱们知道音视频SDK最核心的就是数据在不断的传递,若是说某个模块出现异常,数据没有一种机制能保护它,可能会致使整个SDK的运行出现不稳定的状况。好比在直播场景分发的时候,咱们发现网络有抖动,此时咱们能够调整发送的速率、速度等。

这里简单的画了一下咱们的数据流向,一个是推的方式,就是直接从相机这种模块采集数据,直接着向后面的某个模块去传递。一个是拉的方式,主要是指读取本地文件或者说是间接的获取数据,好比从网络读取数据首先须要将数据读下来,而后再传递到各个模块。这里其实相对于以前说了GPU在异步线程的一些处理,咱们也作了一些兼容和保护,就是防止异步处理时对象被释放的状况发生。因此说咱们基本上也是沿用了GPUImage协议简单的这种思想,而后在此基础上又增长了一些像控制协议的实现机制,让整个链路变得可控。

 

除此以外,咱们也发如今平常过程中,只是简单的设计数据管线,其实并不能很好的对业务场景开发提供帮助,缘由什么呢?

其实咱们只是解决了各个模块数据之间的传递问题,可是并不能对最终产品的场景开发落地解决全部的问题,由于场景跟模块是有很大差别的。咱们所了解的像点播、直播,甚至是特效等,它其实都是通用性的能力。可是实际场景就涉及到各个模块之间的组合,因此数据的传递并不能说传递一个数据,就能把一个场景串联起来。

这里也举了几个平常过程当中典型的例子,好比对于点播的画质优化,会发现类型转换不是那么通畅和简单。对于连麦场景,如何让咱们的SDK或者产品用起来更简单,像人脸特效,好比说涉及到能力的多样化,如何作到兼容性等,这些都不仅是靠数据链路或模块搭载就能解决的。所以咱们提到了一个中间件的概念,就是把数据进行桥接实现资源的共享,在各个模块或业务输出或使用的时候,就能提升总体数据采集或处理的应用性。因此说应用性也是咱们数据采集管线的另外一个考量指标。

特效模块数据中间件设计和实现

接下来咱们来看一下,在实际过程中遇到的一些问题跟解决方案。

 

特效模块一般是典型的PaaS结构,它上面存在多种模型,并且模型之间是能够进行插拔;它还有另一个特色就是耗资源。那么如何在音视频SDK中将这个模块更好的运用起来,去对外提供能力呢?咱们这里以人脸特效的高级美颜接口为例,高级美颜中涉及到的特征点很是多,像大眼、瘦脸、下巴等,并且这些特征点并非经过一次迭代或是一个模型就能解决的,它可能会涉及到屡次的迭代和多种模型的组合叠加。这样就会带来一个问题,在集成特效模块的时候,若是这些能力都是不断变化的,对于模块的使用来讲,就存在一个不安全不稳定的因素。那么咱们该如何把这个问题解决,或者说屏蔽掉呢?

这里咱们提供了一个概念:首先在调用能力的时候,不直接去调用,而是把这些能力作进行抽象、封装,而后这些封装的模型,用来关联后面的一些不一样的算法。由于别人在用SDK的时候,不必定按照你全部东西来集成,可能只使用其中部分能力,这样就有可能会致使一些特效的SDK存在版本不一致。若是没有这个代理层,当出现版本不一致的状况,对于上层来讲可能就会出现大量接口的调整和修改,会比较耗时耗力。不过咱们作代理的话,可能就给屏蔽上层接口的稳定性,而后经过咱们的模型跟一些抽象对象,能够去驱动不一样的AR模块的版本。

经过数据管线咱们能够看到,从录制模块把数据传到effect接口,再把数据再给到AR SDK的时候,每一个AR SDK都会有一个处理检测能力,会定时的检测主屏指标等,为何要这么作?

咱们知道如今的特效,场景、玩法是特别复杂的,处理效果没办法彻底保证。因此为了保证每帧的处理和总体链路的稳定,咱们须要不断作一些性能指标的监测,这些指标监测须要不断的反馈给上层的调用。好比当前数据传输的速度较快,或者传递帧太多处理不完,咱们就能够经过数据管线进行回传,进行控制。甚至能够调整录制模块采集的帧率等,这样再把数据返回给录制模块,录制模块再将数据传递给其它模块,像预览进行渲染等。经过数据管线加代理的方案,咱们就能够很好的整合不一样AR版本、能力,对外保持接口的统一。

连麦模块数据中间件设计和实现

 

连麦模块也是目前音视频产品中用的比较多的一个能力,如在线教育、直播带货、PK、娱乐等等,而且已经慢慢成为一个标准化能力,但连麦模块在使用过程中还存在比较多的问题。

标准直播是单向通讯,且信令相对简单。融合连麦模块后,则会变成双向通讯,信令会变得复杂,媒体流从单路流变成了多路流。如左下图,一个标准的直播SDK加连麦SDK的整合结构,能够看到白色区域是标准的直播流程,从建立直播间到建链、编码、封包,包括经过队列进行分发等等。当融合了连麦能力以后,对整个模块来讲会增长更多的链路。首先会引入RTC的服务端,媒体服务端与信令服务端。另外可能还会引入一些相似于业务系统的消息机制,IM服务器等等。

用户若是发起连麦,能够看到左图红色的箭头。首先它会向RTC信令服务器发送连麦请求,同时也会向IM服务器发送一个请求,向IM服务器发送请求的缘由,主要是为了作一些业务上的处理,好比说UI界面或者场景的一些流程处理。实际上信令服务器主要是为了传递加入房间的请求,请求到达主播直播间后,主播直播间会响应信令服务器,选择赞成或者拒绝。若是赞成,则会经过信令服务器将信号返回给小主播/观众,小主播/观众这个时候就会把数据传递到RTMP的媒体服务器,主播也会把媒体流传到RTMP服务器,两路流汇聚到RTMP服务器后,经过旁路转播等方式来进行对外的转播,此时观众就会看到两个主播或主播和观众合流的画面。

这个实现流程其实比较复杂繁琐,对于用户(主播)来讲可能理解起来很是吃力。首先他要关心的东西特别多,例如 IM服务器、旁路直播,旁路直播还会涉及一些模板的设置等等,他可能都要去关心,因此使用起来很是的麻烦。而对于消费侧的观众来讲,也一样存在一些问题,好比说会出现相同地址,可是会出现不一样编码的两路流,这种状况下可能对于一些播放器来讲会有一些挑战,可能会出现一些卡顿,由于它在不断的重置一些编码参数,另外这种方案经过旁路转推的方式,可能原始的直播流会断掉,就在频繁切换直播流跟混合流的过程中可能就会出现延迟,可能对于拉流侧来讲它就有不少的问题,也不太好。

针对这些问题其实咱们也看到,就是用户在使用过程中其实有不少的不方便,因此说能够看到说简单的数据传递,其实并不能让这个场景作的特别的简单和应用,后面咱们就作了一些大量的端到端的一些数据链的整合,首先是咱们考虑到了就是消费侧的编码播放器的一些问题,首先是切换编码参数,这个方案可能并非一个适用的方案,因此咱们采用了本地混流的技术方案,其好处在于个人推流各方面的参数能够保持一致而不发生变化,而其实对于移动端的处理能力来讲,本地的2~3路流的合流,其实对一些硬件来讲,压力并非特别大。另外咱们把IM服务器跟RTC信令服务器进行了整合,由于咱们以为让用户去关心这么多的信令实际上是没有必要的,并且用户也可能会被这些问题所困扰,因此咱们内部就经过服务端的方式进行了消息的整合,这样子就让用户使用起来变得更加简单。经过这种端到端的一些媒体和信令的整合,其实也是让数据流变得简单,提升了总体接入的应用性。

渲染模块数据中间件设计和实现

 

为何说渲染是音视频SDK关键技术之一?从总体技术链路的角度来讲,渲染模块其实是用户最能感知到的一个模块。在一些复杂的场景下,渲染模块也承载了一些数据的交互和处理。所以渲染模块所包括的处理已经再也不只是渲染,可能会涉及到某些场景的特殊需求,例如清屏等等:多人会议的时候,至关于一个关闭画面的效果;如多路混流,前面提到的RTC混流方案等等,渲染模块也能够做为其中的一个技术方案。也包括像小程序,尤为是一些小程序、安卓的同层设计方案,渲染方案等等。

每一个模块甚至每一个处理的节点,都有可能存在渲染的需求,因此说咱们要将渲染模块进一步拆分。首先经过数据管线的一个能力,把各个模块的数据汇聚到渲染模块,而后再进行数据的处理。这里一般会将一些数据转换成各个平台的处理能力,好比CPU的Buffer,还有一些Surface等等。

数据加工,相对以前的生产流程这是新增的一个节点,它主要是为了应对一些复杂场景,如安卓的同层渲染、Surface的建立与绘制相分离,好比业务模块持有了Surface,可是渲染模块会间接引用并绘制。这里可能还会去作多路流混流的一些参数化配置,能够把每路流做为一个间值进行保存,也会作像图层跟视频帧的绑定,而后经过渲染线程同时绘制多路流。

咱们都知道渲染必需要独立于当前的线程,不然对CPU和总体的开销影响仍是比较大的。所以在建立完GL的环境以后,会按照GL队列将数据进行遍历拆分,来实现单路流,甚至是多路流的绘制。接下来就是一些标准的渲染流程,包括设置渲染的顶点,取渲染纹理,而后新增混合模式,经过绑定纹理来实现OpenGL总体的绘制。