
Open Headunit FFmpeg HEVC软解实战让老设备流畅播放H.265的C/JNI实现【免费下载链接】open-headunitHeadunit App for displaying Android Auto项目地址: https://gitcode.com/GitHub_Trending/he/open-headunitOpen Headunit 是一款把旧安卓平板变成 Android Auto 车机Headunit的开源应用。它内置了一套基于 FFmpeg 的 HEVCH.265软件解码器用 C JNI 实现专门解决老车机芯片没有硬件 H.265 解码能力、手机推送的视频无法流畅播放的痛点。本文带你从原理到源码看懂这套软解方案是怎么落地的。为什么老设备需要 HEVC 软解Android Auto 的投屏视频流常用 H.265HEVC编码——它比 H.264 省 30% 以上带宽但代价是解码复杂度翻倍。问题在于老车机 SoC 无 HEVC 硬解车机芯片如老款 SD821 平台的 MediaCodec 往往只支持 H.264遇到 H.265 流直接黑屏或丢帧软解是唯一出路用 CPU 跑 FFmpeg 的 HEVC 解码器虽然吃算力但配合多线程和快速缩放算法1080p 投屏帧率完全够用老设备因此“满血复活”这正是 Open Headunit 主打“Headunit Revived”的核心价值之一。架构总览Kotlin 与 C 的分工整套方案分为三层职责清晰层级文件职责Kotlin 封装层FfmpegHevcDecoder.kt管理解码器生命周期、JNI 调用、线程数推荐C 解码层ffmpeg_hevc_decoder.cppFFmpeg 解码、swscale 缩放、渲染输出构建层CMakeLists.txt编译hur_soft_hevc共享库链接 FFmpeg数据流向一句话概括Kotlin 把视频字节流喂给 JNI → C 用 FFmpeg 解出 YUV 帧 → swscale 转成 RGBA → 通过 ANativeWindow 画到 Surface 上。C 解码核心三步走第一步找到并打开 HEVC 解码器在 ffmpeg_hevc_decoder.cpp 中HevcDecoder::start()先按名字查找解码器找不到再按编码 ID 兜底const AVCodec* decoder avcodec_find_decoder_by_name(hevc); // 打开前做的三个关键配置 codecContext-thread_count std::max(1, threads); // 多线程切片 codecContext-thread_type FF_THREAD_SLICE; // 切片并行 codecContext-flags2 | AV_CODEC_FLAG2_FAST; // 牺牲精度换速度这里有个实用技巧FF_THREAD_SLICE让多线程在同一帧内切片并行解码比帧间并行延迟更低非常适合实时投屏这种低延迟场景。线程数则由 Kotlin 侧按 CPU 核心数推荐钳制在 2~6 之间见 FfmpegHevcDecoder.kt 的recommendedThreadCount()避免低配车机开太多线程反而卡顿。第二步送包收帧的标准 FFmpeg 流水线decode()函数L98-L126严格遵循 FFmpeg 的 send/receive 模型av_new_packetGetByteArrayRegion把 Kotlin 传来的字节拷入 AVPacket并打上时间戳pts/dtsavcodec_send_packet送入解码器遇到EAGAIN就先去收帧receiveFrames()循环调用avcodec_receive_frame把解出的 YUV 帧逐张交给渲染函数。第三步缩放渲染两条输出路径renderFrame()提供了两种输出方式这是设计上的亮点Surface 直出路径默认用sws_getCachedContextSWS_FAST_BILINEAR快速双线性缩放把 YUV 转成 RGBA再逐行memcpy进ANativeWindow锁定后的缓冲区unlockAndPost上屏。快速双线性是“质量与速度”的折中投屏场景人眼几乎看不出差别YUV 回调路径GLES 视图模式通过 JNI 回调onNativeYuv420Frame把 Y/U/V 三个平面包装成DirectByteBuffer传回 Kotlin 层FfmpegHevcDecoder.kt交给 SoftwareYuvFrameSink.kt 用 OpenGL 上传纹理渲染。这样省掉了 CPU 上的 YUV→RGBA 转换把色彩空间转换丢给 GPU老设备上更省 CPU。JNI 层四个 native 方法撑起全部能力Kotlin 侧只声明了 4 个external函数接口极简private external fun nativeIsAvailable(): Boolean private external fun nativeCreate(surface: Surface?, callback: FfmpegHevcDecoder, useYuvCallback: Boolean, width: Int, height: Int, threadCount: Int): Long private external fun nativeDecode(handle: Long, buffer: ByteArray, offset: Int, size: Int, presentationTimeUs: Long): Int private external fun nativeRelease(handle: Long)设计上两个细节值得学习handle 模式nativeCreate返回一个jlong句柄其实就是 CHevcDecoder*指针后续decode/release都凭句柄操作避免每次传递复杂对象优雅降级C 侧用HUR_HAVE_FFMPEG宏编译。如果某个 ABI 没有打包 FFmpeg 库比如只编了 x86 模拟器版所有 native 函数直接返回空值Kotlin 侧isAvailable()返回 false应用自动回退到设备 MediaCodec 软解不会崩溃。构建与集成FFmpeg 如何进包CMakeLists.txt 展示了精简集成的完整流程目标hur_soft_hevc由单一 C 文件编译启用 C17 与严格告警FFmpeg 头文件放在 app/src/main/cpp/ffmpeg/include预编译的libavcodec.so、libavutil.so、libswscale.so放在 jniLibs/arm64-v8aCMake 逐一检查必需文件是否存在缺任何一个就定义HUR_HAVE_FFMPEG0实现“有则用、无则静默”链接参数加-Wl,-z,max-page-size16384适配 Android 15 的 16KB 内存页。FFmpeg 官方构建参数推荐见 ffmpeg/README.md思路是--disable-everything后只开启hevc解码器 swscale把 .so 体积压到最小——对车机这种存储紧张的环境非常关键。许可证方面打包的 FFmpeg 为LGPL 2.1随动态链接使用符合 LGPL 要求见 FFMPEG_LICENSE.md。在应用里怎么启用无需改一行代码。在 Open Headunit 的设置页中选择“强制软解 捆绑 FFmpeg 解码器”即可。核心判断逻辑在 VideoDecoder.ktprivate fun shouldUseBundledHevc(type: CodecType, forceSoftware: Boolean): Boolean { return type CodecType.H265 forceSoftware settings.softwareVideoDecoder Settings.SoftwareVideoDecoder.BUNDLED_FFMPEG FfmpegHevcDecoder.isAvailable() }当手机协商出 H.265 流、设备硬解不支持、且用户选择了捆绑 FFmpeg 时startBundledHevc()就会接管渲染如果 GLES 视图模式生效还会自动切换到 YUV 回调 GPU 上传的加速路径。小结Open Headunit 的 HEVC 软解方案给“用 C/JNI 给老设备补硬件短板”提供了一个教科书式的范例✅FFmpeg send/receive 标准流水线代码短小易读✅切片多线程 FAST 模式针对实时低延迟场景调优✅双输出路径Surface 直出走 CPU 缩放GLES 模式走 YUV 回调 GPU 渲染✅编译期优雅降级缺库不崩、自动回退✅最小化打包只带 hevc 解码器体积与许可证都友好。如果你想在自己的 Android 项目里为老设备补一个 H.265 软解能力直接参考 app/src/main/cpp/ 目录下的这份实现改造成本极低。【免费下载链接】open-headunitHeadunit App for displaying Android Auto项目地址: https://gitcode.com/GitHub_Trending/he/open-headunit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考