JNI 边界:类与方法初始化一次抓齐,反向回调留一个入口
Android 这一侧绕不开 JNI,而边界怎么划决定了后面好不好写。这里的做法是:C++ 侧在初始化时一次性把用到的 Java 类与方法都抓齐,存进几个结构体里,之后打开 Surface 与纹理只查表调用,不再零散地 FindClass。
抓的东西分三组:SurfaceTexture 那一组(初始化、绑定与解绑 GL 上下文、取变换矩阵、释放等)、一个自研的包装对象(用来接帧可用回调)、以及 Surface 的构造。第三组是关键——Java 侧的 Surface 对象要由 SurfaceTexture 造出来,C++ 侧拿到的则是从它转出来的原生窗口。
反过来的方向留一个门:那个包装对象持有一个原生指针,Java 侧收到帧可用回调时把它回传给 C++,C++ 再转成内部回调。Java 侧释放时会加锁把这个指针清零,免得释放之后还有回调进来撞空指针。
打开一个解码器:创建、配置、启动

第一段是创建。按编码类型映射到平台的 MIME 串,再按类型建解码器。其中 VP9 与 AV1 多一步:这两个编码在这类设备上常被平台软实现顶掉,所以会先取一遍候选名,把名字里带平台软实现前缀的排除掉,排到没有硬实现时再回退软解。
第二段是配置,三样东西:宽高从上层传下来的参数取;色彩格式按上层要的类型请求(NV12 还是 10bit 那一档,取决于场源与显示链要什么);参数集按编码类型分流送进去。
第三段是启动。启动之后还有一个车道守卫:有些驱动在启动之后翻脸,把解码器推进「已释放」状态,这种失败按打开失败上报,好让上层换道,而不是当成普通的解码错误。
参数集先转一次:长度前缀转起始码,只改本地副本
上层交下来的参数集是按长度前缀包的,而这类解码器要的是起始码形式,所以得先转一遍。
这里有一处讲究:转换只改本地那一份副本。因为这份参数集同时是留给下一条车道的——如果原地改了,等到换成 FFmpeg 那条路时,它拿到的就不是它要的格式了。踩过这个坑之后,副本和原件分开。
分流的规则按编码各不相同:H264 把 SPS 与 PPS 拆到两个槽里,缺一个就报没有配置;H265 是三个槽;AV1 的参数集本身是一段配置记录,原样送;VP9 没有带外参数集,只需要设编码类型。
Surface 的两条来路

解码这一侧自己建一条链路:先起一个 EGL 上下文,建一个 SurfaceTexture 并给它一张外部纹理,再把它转成原生窗口交给解码器。之后解码器每出一个缓冲,就把这一帧更新到那张外部纹理上。
呈现那一侧的 Surface 不由解码器管,它由宿主给:Java 侧的视图把 Surface 交下来,C++ 侧转成原生窗口,再挂到渲染对象上。
这两条来路决定了出帧的形态:关系到一个原生窗口是否为空——有窗口时走外部纹理那条路,交出去的是 GPU 帧;没有窗口时(比如在控制台里跑、没有 JNI 环境)落到字节缓冲那条路,交出 CPU 帧。这个分叉在打开的时候就定下来了。
失效与重建不各走一套。渲染对象的 Surface 指针一变,就置一个重置标志;渲染线程在重建入口读这个标志,该拆的时候拆、该重开的时候重开。换屏、尺寸变化、上下文换新,都走这一条链。这条链有个前提:标志由设置 Surface 的那条线程置位,由渲染线程消费。
seek 时这一路原地 flush,不重建解码器
这一档是这个实现与别的平台最大的差别。
上层发来 flush 时,先看当前这条解码车道是谁:如果是 Android 这个实现,就置一个标志、原地处理,不重建解码器;换作别的硬解实现(比如桌面那条 D3D11 的路),就整体重建解码上下文。
判断的依据在帧的持有方:这类解码器的实例与它那套 Surface 与上下文全在,路径上不需要拆图重建,旧的帧还能继续在屏上;而另一条路上,刷新缓冲清不掉解码器内部的重排信息,不重建下一帧就是错的。

原地 flush 的动作有三步:先清掉跨调用攒下的补喂队列(这份残留会在落点之前被喂进去,把画面顶到下一个关键帧),再调解码器自己的刷新,最后把车道守卫那个窗口重开一遍——这里只重开窗口,已经判定为「死」的那一档不动。
出帧与回收
GPU 那条路出帧时不释放输出缓冲:帧对象里带着一个队列下标,缓冲留到渲染侧处理完再还。还的时候如果这一帧已经渲染了,同时把外部纹理更新一次。
CPU 那条路当场取数据:按输出缓冲的跨距和切片高度把各平面切出来,NV12 这一档是半平面的双面结构,其它几种是三平面;取完立刻把缓冲还回去。
还有一处安排:解码线程里的 EGL 上下文是主,渲染那边的上下文是辅。共享关系反过来的话,渲染侧拿不到解码出的那张外部纹理。
引擎 avox 在 GitHub 开源:github.com/xxxzhou/avox,Android 这一侧的 JNI 桥、参数集分流与原地 flush 都在里面。
音视频开发 播放器 硬件解码 MediaCodec JNI Android