自己的采集与硬编,接上 WebRTC 的轨道与编码器

从零做一个全平台播放器·约 1532 字

前几篇都是拉流这头:WebRTC 的流进来,解码渲染归播放器。这篇是推流那头:WebRTC 自带的源和编码器用不上项目已有的东西——各平台的硬编、截屏与摄像头这些采集源、滤镜水印那层图像处理。不改它的网络层,又要把这些用上,做法是在中间架四个适配器:两个把项目的源桥成它的轨道源,两个把项目的编码器桥成它的编码器接口,顶上一个统一入口把信令、数据流和渲染拼在一起。这篇讲四个适配器各接在哪、数据怎么走、码率谁说了算。

四个适配器,一边一个

项目侧已有的接口是固定的:数据源有采集回调那一族,编码器有输入、释放和编码包回调。WebRTC 侧要的也是固定的:轨道源、音频源接口、视频音频编码器接口。两边对不上,适配器各接一个:

视频源与音频源,桥成它的轨道源与音频源接口;视频编码器与音频编码器,桥成它的编码器接口。再往上,一个统一入口把信令、两条数据流和渲染整合起来,WebRTC 内部跑的就全是项目自己的源和编解码器。

项目侧的数据源与编解码器,经四个适配器映射到 WebRTC 的接口

视频源:一次采集,两边各取一份

视频源适配器内部挂了一个广播器:收到的每一帧同时发给所有消费者——本地预览窗口一份,编码器一份。数据只采一次,预览和编码各取一份,CPU 和 GPU 都省。

资源是跟着消费者走的:第一个消费者注册时,才打开底层采集源、起本地预览窗口;最后一个走了,自动关掉。挂着的图像处理是现成的那套——本地预览与送编码前的画面走同一趟渲染,裁剪缩放、水印、滤镜都在里头。

输出形态按编码器吃什么定。能直接吃 GPU 纹理的硬编,比如 Apple 的 VideoToolbox,纹理直接给,不往 CPU 落(引用计数加一道,防着纹理提前释放);要 CPU 数据的,用计算着色器把 RGBA 转成要的 YUV 格式,就是普通播放里渲染出图用的那一路。Windows 单独说:硬编暂时没有直接吃 D3D11 纹理的输入口,所以先落到 CPU 的 NV12,再按显卡交给对应的硬编——软编兜底时则转成 yuv420p。落到哪一种,由源侧一个输出形态的开关决定,起编码器之前设好,链路就按它走。这与普通播放那边硬编的结论是同一条:编码器是硬的,纹理直喂那条路还没接上。

视频源:一次采集,预览与编码各取一份,输出按硬编形态分岔

音频源:归一与切片

音频源适配器一身三职:WebRTC 的音频源接口、播放器侧的采集回调、加上那个归一与切片的重整形器。归一规则与音频前处理那一篇相同——44100 改写成 48000、转 S16、声道收到二,然后切成 10ms 一段,按位深、采样率、声道数一帧一帧喂给 WebRTC。消费者的开关逻辑与视频源一样:有人才开,没人就关。

视频编码器:进和出各转一道

编码器适配器实现 WebRTC 的编码器接口,里头包着项目自己的编码器,覆盖各平台 H264、H265 的硬编。建的时候按平台与硬编配置从注册表取默认名——Apple 取 VideoToolbox 的 H264,别的平台各有各的默认;起编码器时它会把初始码率一并给下来,设进底层,之后的调整都走码率回调那条路。取不到默认名就报错,不做运行期降级,候选链那套降级机制挂在录制推流那条腿上,这条路没接。音频编码器那一侧与视频同构:声明的格式列表与解码侧一致,协商到 AAC 就用项目自己的编码器。

进来的帧先转一道:WebRTC 的帧由一个双模式缓冲承载,CPU 模式是 YUV 帧,GPU 模式带着纹理;原生帧直接进硬编吃纹理那条路。出去再封一道:编码包回调里把项目的包封成它的编码帧——RTP 时间戳从进帧时记下的对应关系里取,采集时间用包自带的 pts,帧类型按 NAL 单元判是不是关键帧,头里标上编码类型。Apple 硬编吐的是长度头装法,这里判过一次之后统一转成起始码,后面的 RTP 打包就顺了。

码率是 WebRTC 说了算:它按网络状况算出目标码率,回调里取出来设到底层编码器。编码器只管照办,自己不另做一套码控。关键帧也是它要:网络一抖、或者有新端点加入,它会发来「下一帧要是关键帧」的请求,适配器把这个请求原样转给编码器——画面能在丢包之后很快恢复,靠的就是这一下。

从源帧到 RTP:进出的两道转换,以及码率从 WebRTC 回填

顶层把两边拼起来

统一入口实现播放器接口,同时是命令执行体和事件观察者——上一章框架里那几个角色它都在当,只是里头的源和渲染换成了 WebRTC 这一套:信令与数据流、本地画面、远端画面、远端声音四样整合在一起。角色分两种:发起方主动 CreateOffer,适合拿 HTTP 接口换 SDP 的推流;应答方被动等 offer 再作答,适合有长连接通道的通话。选角色就一个调用,它据此调整本地与远端描述的设置顺序:发起方先出 offer、设本地,收到对方 answer 再设远端;应答方反过来,先把收到的 offer 设成远端,再作答、设本地——顺序乱了,协商就死在一半。

渲染全是复用:本地预览、远端画面、远端音频,取的都是项目原有的那几个渲染器,底下是同一套 Vulkan 图像处理与硬件渲染。用法上设好数据源、调 open 就起来了;自带的示例里,音频取 Windows 声卡设备,视频取屏幕采集设备,地址用带推流标记的 WebRTC 接口,十几行就把一次推流跑通。

这一章到这儿齐了:库怎么接进工程、录音的回声噪音、拉流侧自己的解码与渲染、推流侧自己的采集与硬编,各有各的一篇;WebRTC 只负责网络这一段,弱网里把数据送过去。

引擎 avox 在 GitHub 开源:github.com/xxxzhou/avox,四个适配器与统一入口都在里面。

音视频开发 WebRTC 推流 硬件编码 采集 C++ 实时通信