会话的建与配

实现按编码注册进编码器注册表,标成硬件;上层按默认名取到的就是它。建会话前先把统一的编码标识翻成它认的类型常量,然后带上分辨率建会话,回调也是这时候挂上去的——同样是把自己的指针当不透明参数带进去的写法(硬解接入那一篇里有同一种处理)。
配的属性有六样:实时模式打开、档次(低压缩比那种用基线档,高压缩比那种用主档)、不允许帧重排(等于禁掉双向预测帧)、平均码率、期望帧率,以及最大关键帧间隔——最后这个按关键帧间隔与帧率的乘积算,上层不给间隔就用基类的默认值。
码率不用手工给:算这件事归基类,上层不设的时候按分辨率与帧率算一个,与另外两端同一套算法,实现只收结果。码控也只给了平均码率这一样,恒定码率、质量档那一类键都没有设,平台内部怎么权衡,不替它下结论。
有两处版本门控:太老的系统不支持硬编,稍老一些的不支持高压缩比那一种,都按「不支持」上报,交给上层换候选。
配完属性还要显式声明一次「准备编码」——这一步之前送帧不收,声明之后才开始送。
三条输入路
编码器有三个入帧重载,对应三种帧,走哪条由帧自带的类型定,调用方不用预先声明;这样同一套编码器能接住录制链上不同来路的帧:
帧里带那个共享面的(管线处理过的结果),从面上包出一个像素缓冲再送进去。这条路也是三条里最年轻的:包缓冲时传的属性是空的,没有向编码器声明期望的输入格式,各机型与分辨率是不是都认这套转换,代码看不出来,也没有真机验证过。
帧里直接就是系统像素缓冲的(相机直出那条,见采集那一篇),直接送——不额外拷。
帧里是内存平面数据的,建一个像素缓冲、把各平面拷进去、送完再放掉。
那条「经过 Vulkan」的路,共享出来的是一个 BGRA 的面,不是 Metal 纹理;编码器自己不认识 Vulkan,它只是拿到一块能包成像素缓冲的显存。这也是硬解帧那两条去处里说的那个面——方向反过来用。
产物:码流、时间戳、关键帧

回调里做四件事:
取码流。从样本的数据块里拿到指针与长度,组一个码流包,前缀按四字节起始码算。
算时间戳。把系统给的呈现时间转成毫秒:先转成秒(这一步用浮点),再乘一千。这里踩过一个坑:早先拿这个值的整数部分直接除以刻度,不到一秒的那部分被截掉,而一帧也就几十毫秒,整条片子的时间戳全是零。这个值到封装那边还要再归零、换到流自己的时间基,这一层只要保证不丢精度;解与编码两侧的取法一致。
判丢帧。回调给出的标志里带丢帧信息时直接返回,不往下走。
判关键帧。看样本的附件里有没有那个「不同步」的键:有就是普通帧,没有就是关键帧。
配置帧:回调不带参数集,自己补
这个接口的编码回调不带参数集,配置帧要自己补:在关键帧那一刻,从格式描述里按索引把参数集取出来——低压缩比的编码是两样,高压缩比那种是三样,顺序固定,封装那边按这个顺序读。每样前面加一个四字节的长度头,用网络字节序的长整数。
补发的时机卡在关键帧那一刻,普通帧不补;流中间的解码方要是丢了配置帧,要等下一个关键帧才拿得到。这一段不能省:封装那边要等配置帧才建流,少了它,写出去的文件头就是空的。还有一处要对齐:这里的码流是长度前缀形式,不是起始码形式——编码器交出来的就是这个形式,自己补的那几样也要跟它一致,否则下一个读的人会按前缀去切、切成错的边界。
关流:先丢迟到回调,再排空在飞帧
关流的时候先置一个标志,之后迟到的编码回调直接丢掉;关之前让会话把在飞的帧排空一次,免得释放会话时还有回调在用那些资源。
与解码侧共用参数集工具与版本探测
两侧各建各的会话,不共享会话对象;真正共用的是同一套参数集处理工具与系统版本探测。这与另外两个平台一致——硬解与硬编共用的是设备与纹理那一层,而不是编解码器对象。
这一侧交出去的码流包进的是同一条封装路(录制链那一篇),配置帧那几包决定封装什么时候能建流;时间戳的取法则要与整条链上的其他来源一致——采集那一侧的帧用的是本地时钟,这一侧用的是系统给的呈现时间,两个来源混在一条流里时,首包归零那一步取的是首包的解码时间戳,所以偏差不会被放大,但也不会被消掉。
与解码那一侧共用的是参数集工具与版本探测;与硬解帧那两条去处共用的是那个面。
引擎 avox 在 GitHub 开源:github.com/xxxzhou/avox,这套压缩会话的配置与那三路输入都在里面。
音视频开发 播放器 硬件编码 VideoToolbox IOSurface iOS