一个压缩会话,六样属性
实现按编码注册进编码器注册表,标成硬件,上层按默认名取到的就是它。Apple 这一侧的硬编是一个压缩会话包办:先把统一的编码标识翻成它认的类型常量,带上分辨率建会话,回调把自己的指针当不透明参数带进去;再配六样属性,最后显式声明一次「准备编码」,送帧才开始收。输入这一侧它收三种帧——管线处理过的、相机直出的、内存里的平面数据——同一套编码器就接住了录制链上不同来路的帧。这篇写会话配的哪几样、三条输入路各走哪条,以及配置帧为什么要自己补。
配的属性有六样:实时模式打开、档次(低压缩比那种用基线档,高压缩比那种用主档)、不允许帧重排(等于禁掉双向预测帧)、平均码率、期望帧率,以及最大关键帧间隔——最后这个按关键帧间隔与帧率的乘积算,上层不给间隔就用基类的默认值。
码率不用手工给:算这件事归基类,上层不设的时候按分辨率与帧率算一个,与另外两端同一套算法,实现只收结果。码控也只给了平均码率这一样,恒定码率、质量档那一类键都没有设,平台内部怎么权衡,不替它下结论。
有两处版本门控:太老的系统不支持硬编,稍老一些的不支持高压缩比那一种,都按「不支持」上报,交给上层换候选。
配完属性还要显式声明一次「准备编码」——这一步之前送帧不收,声明之后才开始送。
三条输入路,按帧自带的类型分
编码器有三个入帧重载,对应三种帧,走哪条由帧自带的类型定,调用方不用预先声明;这样同一套编码器能接住录制链上不同来路的帧:
帧里带那个共享面的(管线处理过的结果),从面上包出一个像素缓冲再送进去。这条路也是三条里最年轻的:包缓冲时传的属性是空的,没有向编码器声明期望的输入格式,各机型与分辨率是不是都认这套转换,代码看不出来,也没有真机验证过。
帧里是内存平面数据的,建一个像素缓冲、把各平面拷进去、送完再放掉。
帧里直接就是系统像素缓冲的(相机直出那条,见采集那一篇),直接送——不额外拷。
那条「经过 Vulkan」的路,共享出来的是一个 BGRA 的面,不是 Metal 纹理;编码器自己不认识 Vulkan,它只是拿到一块能包成像素缓冲的显存。这也是硬解帧那两条去处里说的那个面——方向反过来用。
回调里做四件事:组包、算时间、判帧
回调里做四件事:
取码流。从样本的数据块里拿到指针与长度,组一个码流包,前缀按四字节起始码算。
算时间戳。把系统给的呈现时间转成毫秒:先转成秒(这一步用浮点),再乘一千。这里踩过一个坑:早先拿这个值的整数部分直接除以刻度,不到一秒的那部分被截掉,而一帧也就几十毫秒,整条片子的时间戳全是零。这个值到封装那边还要再归零、换到流自己的时间基,这一层只要保证不丢精度;解与编码两侧的取法一致。
判丢帧。回调给出的标志里带丢帧信息时直接返回,不往下走。
判关键帧。看样本的附件里有没有那个「不同步」的键:有就是普通帧,没有就是关键帧。
配置帧要自己补,补在关键帧那一刻
这个接口的编码回调不带参数集,配置帧要自己补:在关键帧那一刻,从格式描述里按索引把参数集取出来——低压缩比的编码是两样,高压缩比那种是三样,顺序固定,封装那边按这个顺序读。每样前面加一个四字节的长度头,用网络字节序的长整数。
补发的时机卡在关键帧那一刻,普通帧不补;流中间的解码方要是丢了配置帧,要等下一个关键帧才拿得到。这一段不能省:封装那边要等配置帧才建流,少了它,写出去的文件头就是空的。还有一处要对齐:这里的码流是长度前缀形式,不是起始码形式——编码器交出来的就是这个形式,自己补的那几样也要跟它一致,否则下一个读的人会按前缀去切、切成错的边界。
关流先置标志,再排空在飞的帧
关流的时候先置一个标志,之后迟到的编码回调直接丢掉;关之前让会话把在飞的帧排空一次,免得释放会话时还有回调在用那些资源。
和解码侧共用的是参数集工具与版本探测
这一侧交出去的码流包进的是同一条封装路(录制链那一篇),配置帧那几包决定封装什么时候能建流;时间戳的取法则要与整条链上的其他来源一致——采集那一侧的帧用的是本地时钟,这一侧用的是系统给的呈现时间,两个来源混在一条流里时,首包归零那一步取的是首包的解码时间戳,所以偏差不会被放大,但也不会被消掉。
两侧各建各的会话,不共享会话对象;真正共用的是同一套参数集处理工具与系统版本探测。这与另外两个平台一致——硬解与硬编共用的是设备与纹理那一层,而不是编解码器对象。
与解码那一侧共用的是参数集工具与版本探测;与硬解帧那两条去处共用的是那个面。
引擎 avox 在 GitHub 开源:github.com/xxxzhou/avo…,这套压缩会话的配置与那三路输入都在里面。
系列《从零做一个全平台播放器》按施工顺序发,本篇是 4.4。 上一篇:4.3 Apple 的相机与麦克风落在同一套采集会话上 juejin.cn/post/770415…