尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ExoPlayer FFmpeg 音频解码扩展构建与接入指南:基于 SmartTube 内置 exoplayer-amzn-2.10.6 源码

ExoPlayer FFmpeg 音频解码扩展构建与接入指南:基于 SmartTube 内置 exoplayer-amzn-2.10.6 源码 ExoPlayer FFmpeg 音频解码扩展构建与接入指南基于 SmartTube 内置 exoplayer-amzn-2.10.6 源码【免费下载链接】SmartTubeBrowse media content with your own rules on Android TV项目地址: https://gitcode.com/GitHub_Trending/smar/SmartTube导读本文以 SmartTube 仓库内随附的 exoplayer-amzn-2.10.6/extensions/ffmpeg/README.md 为骨架完整讲解 ExoPlayer FFmpeg 扩展FfmpegAudioRenderer从 FFmpeg 原生库交叉编译、JNI 封装构建到接入DefaultRenderersFactory的完整流程并结合本仓库的FfmpegLibrary、FfmpegDecoder、ffmpeg_jni.cc等源码剖析其底层解码原理。读完本文你将掌握如何在 Android TV / Android 应用中为 ExoPlayer 增加 MediaCodec 之外的多格式音频软解能力并理解其渲染器优先级与格式支持矩阵。说明SmartTube 以 git submodule 方式内置了这份 ExoPlayer 分支settings.gradle 中gradle.ext.exoplayerRoot new File(rootDir, ./exoplayer-amzn-2.10.6)并在 exoplayer-amzn-2.10.6/core_settings.gradle 中通过include :exoplayer-extension-ffmpeg将 FFmpeg 扩展注册为独立 Gradle 模块。下文所有源码路径均以仓库根目录为基准。一、扩展定位FFmpeg 音频渲染器能做什么FFmpeg 扩展为 ExoPlayer 提供了一个FfmpegAudioRenderer位于包com.google.android.exoplayer2.ext.ffmpeg它使用 FFmpeg 完成音频解码能够渲染多种编码格式的音频。与系统自带的MediaCodecAudioRenderer不同FFmpeg 软解不受设备硬件编解码器能力限制因而在 Android TV 等对 AC-3、E-AC-3、DTS、TrueHD 等格式支持不一的设备上可作为可靠的兜底方案。扩展模块共包含 4 个 Java 类与 1 个 JNI 源文件文件职责FfmpegLibrary.java加载并查询底层原生库版本、解码器可用性、MIME→codec 映射FfmpegAudioRenderer.java基于SimpleDecoderAudioRenderer的音视频渲染器实现FfmpegDecoder.java驱动原生解码器的SimpleDecoder封装负责缓冲区与 extra data 组装FfmpegDecoderException.java解码异常类型ffmpeg_jni.ccJNI 桥接层直接调用 libavcodec / libavresample C API二、许可证提示务必先读仓库内代码本身基于 Apache 2.0 许可但使用本扩展还要求构建并引入一个或多个外部库即 FFmpeg 本体这些外部库拥有独立的许可证LGPL/GPL需要在使用方自行评估合规性。README 中对此有明确提示代码许可与所链接的 FFmpeg 库许可是两回事发布产物时必须同时满足两者的许可要求。三、构建前置条件3.1 本地依赖 ExoPlayer 模块要使用该扩展需要先取得 ExoPlayer 源码本仓库已内置为exoplayer-amzn-2.10.6/并在本地依赖其各个 library 模块。README 特别指出该扩展不通过 JCenter 提供详见上游 issue #2781因此必须走源码本地构建路径。在本仓库中这一步骤已由 Gradle 配置完成settings.gradle 将exoplayer-amzn-2.10.6声明为exoplayerRoot并施加exoplayerModulePrefix exoplayer-exoplayer-amzn-2.10.6/core_settings.gradle 将扩展模块映射到extensions/ffmpeg目录模块名称为:exoplayer-extension-ffmpeg。3.2 设置环境变量首先进入 ExoPlayer 检出目录定义扩展的 JNI 源码路径cd path to exoplayer checkout FFMPEG_EXT_PATH$(pwd)/extensions/ffmpeg/src/main/jni在本仓库中path to exoplayer checkout即exoplayer-amzn-2.10.6因此等价于FFMPEG_EXT_PATH$(pwd)/exoplayer-amzn-2.10.6/extensions/ffmpeg/src/main/jni3.3 准备 Android NDK下载 Android NDK 并设置其位置环境变量。当前仅支持至 NDK 15c 的版本旧版 NDK 的 4.9 工具链与ndk-build体系与下文Application.mk中的APP_STL : gnustl_static、APP_PLATFORM : android-9等配置配套NDK_PATHpath to Android NDK3.4 设置宿主平台按构建机操作系统设置HOST_PLATFORM它决定 NDK 预编译工具链目录名HOST_PLATFORMlinux-x86_64 # Linux x86_64 # HOST_PLATFORMdarwin-x86_64 # Mac OS X四、获取并构建 FFmpeg 原生库4.1 理解COMMON_OPTIONS配置标志FFmpeg 的./configure标志决定了最终支持的格式必须在构建前规划好。README 推荐的基线选项如下各标志含义标志作用--target-osandroid指定目标系统为 Android--disable-static --enable-shared产出.so动态库供System.loadLibrary加载--disable-doc --disable-programs不构建文档与命令行工具减小体积--disable-everything关闭一切组件作为裁剪起点--disable-avdevice --disable-avformat --disable-swscale --disable-postproc --disable-avfilter --disable-symver按需关闭非解码路径组件本扩展只做音频解码--disable-swresample --enable-avresample使用 libavresample 而非新式 swresampleffmpeg_jni.cc中调用的是avresample_*API见 ffmpeg_jni.cc--enable-decodervorbis/opus/flac白名单式启用解码器每加一种格式就补一行--enable-decoder...4.2 构建 FFmpeg release/4.0三架构示例以下命令来自 README演示在 Linux x86_64 上为armeabi-v7a、arm64-v8a、x86三个 ABI 依次交叉编译 FFmpeg 4.0COMMON_OPTIONS\ --target-osandroid \ --disable-static \ --enable-shared \ --disable-doc \ --disable-programs \ --disable-everything \ --disable-avdevice \ --disable-avformat \ --disable-swscale \ --disable-postproc \ --disable-avfilter \ --disable-symver \ --disable-swresample \ --enable-avresample \ --enable-decodervorbis \ --enable-decoderopus \ --enable-decoderflac \ \ cd ${FFMPEG_EXT_PATH} \ (git -C ffmpeg pull || git clone git://source.ffmpeg.org/ffmpeg ffmpeg) \ cd ffmpeg git checkout release/4.0 \ ./configure \ --libdirandroid-libs/armeabi-v7a \ --archarm \ --cpuarmv7-a \ --cross-prefix${NDK_PATH}/toolchains/arm-linux-androideabi-4.9/prebuilt/${HOST_PLATFORM}/bin/arm-linux-androideabi- \ --sysroot${NDK_PATH}/platforms/android-9/arch-arm/ \ --extra-cflags-marcharmv7-a -mfloat-abisoftfp \ --extra-ldflags-Wl,--fix-cortex-a8 \ --extra-ldexeflags-pie \ ${COMMON_OPTIONS} \ \ make -j4 make install-libs \ make clean ./configure \ --libdirandroid-libs/arm64-v8a \ --archaarch64 \ --cpuarmv8-a \ --cross-prefix${NDK_PATH}/toolchains/aarch64-linux-android-4.9/prebuilt/${HOST_PLATFORM}/bin/aarch64-linux-android- \ --sysroot${NDK_PATH}/platforms/android-21/arch-arm64/ \ --extra-ldexeflags-pie \ ${COMMON_OPTIONS} \ \ make -j4 make install-libs \ make clean ./configure \ --libdirandroid-libs/x86 \ --archx86 \ --cpui686 \ --cross-prefix${NDK_PATH}/toolchains/x86-4.9/prebuilt/${HOST_PLATFORM}/bin/i686-linux-android- \ --sysroot${NDK_PATH}/platforms/android-9/arch-x86/ \ --extra-ldexeflags-pie \ --disable-asm \ ${COMMON_OPTIONS} \ \ make -j4 make install-libs \ make clean关键点拆解--libdirandroid-libs/abi把每个 ABI 的产物分别输出到ffmpeg/android-libs/abi/供后续Android.mk以预编译库方式引用见 Android.mk 的LOCAL_SRC_FILES : ffmpeg/android-libs/$(TARGET_ARCH_ABI)/$(LOCAL_MODULE).so。不同 ABI 的工具链前缀与 sysroot 不同armv7-a 用arm-linux-androideabi-4.9android-9arm64 用aarch64-linux-android-4.9android-21x86 用x86-4.9android-9。--extra-ldflags-Wl,--fix-cortex-a8仅 armeabi-v7a 需要规避 Cortex-A8 的 erratax86 加--disable-asm避免内联汇编兼容问题。每次架构构建完成后执行make clean再重新 configure避免污染下一次构建。五、构建 JNI 原生库FFmpeg 本体构建完成后还需把 JNI 桥接代码编译进libffmpeg.socd ${FFMPEG_EXT_PATH} \ ${NDK_PATH}/ndk-build APP_ABIarmeabi-v7a arm64-v8a x86 -j4APP_ABI必须覆盖上一步构建过的架构。该步读取同一目录下的两个 NDK 构建脚本Android.mk声明libavcodec、libavutil、libavresample三个预编译共享库直接打包android-libs/abi/下的.so随后将ffmpeg_jni.cc编译为模块ffmpeg链接上述三库并带上-llog。这与 FfmpegLibrary.java 中new LibraryLoader(avutil, avresample, avcodec, ffmpeg)的加载顺序一一对应。Application.mkAPP_OPTIM : release、APP_STL : gnustl_static、APP_PLATFORM : android-9最低 API 9。5.1 ProGuard 规则扩展自带的 proguard-rules.txt 只有一条关键规则-keepclasseswithmembernames class * { native methods; }防止混淆器重命名native方法从而保证 JNI 符号与ffmpeg_jni.cc中的Java_com_google_android_exoplayer2_ext_ffmpeg_*名称匹配。六、在播放器中接入FfmpegAudioRenderer构建完成后需要显式告知 ExoPlayer 使用FfmpegAudioRenderer。具体方式取决于你使用的 Player API6.1 方式一DefaultRenderersFactoryExoPlayerFactory.newSimpleInstance在DefaultRenderersFactory构造函数中设置extensionRendererModeDefaultRenderersFactory factory new DefaultRenderersFactory(context, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON); ExoPlayer player ExoPlayerFactory.newSimpleInstance(context, factory);EXTENSION_RENDERER_MODE_ON当MediaCodecAudioRenderer不支持输入格式时回退使用FfmpegAudioRenderer播放EXTENSION_RENDERER_MODE_PREFER优先使用FfmpegAudioRenderer优先级高于MediaCodecAudioRenderer。6.2 方式二子类化DefaultRenderersFactory覆写buildAudioRenderers把FfmpegAudioRenderer追加到输出列表末尾。ExoPlayer 会选择列表中第一个支持输入媒体格式的RendererOverride protected void buildAudioRenderers( Context context, ExtensionRendererMode int extensionRendererMode, MediaCodecSelector mediaCodecSelector, DrmSessionManagerExoMediaCrypto DrmSessionManagerExoMediaCrypto drmSessionManager, boolean playClearSamplesWithoutKeys, Handler eventHandler, AudioRendererEventListener eventListener, ArrayListRenderer out) { super.buildAudioRenderers(...); out.add(new FfmpegAudioRenderer(eventHandler, eventListener)); }6.3 方式三自定义RenderersFactory在createRenderers中返回包含FfmpegAudioRenderer实例的数组播放器同样选择第一个支持输入格式的 Renderer。6.4 方式四ExoPlayerFactory.newInstance直接向 Renderer 数组传入FfmpegAudioRenderer实例FfmpegAudioRenderer audioRenderer new FfmpegAudioRenderer(); ExoPlayer player ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer, ...});6.5 自定义 TrackSelector 的注意事项以上假设你使用DefaultTrackSelector。若使用自定义 track selectorRenderer 的选择逻辑由你的实现决定因此你需要确保向播放器传入了FfmpegAudioRenderer并在自己的逻辑中为特定 track 选用该 renderer。七、源码级原理剖析7.1 格式支持矩阵FfmpegLibrary.getCodecNameJava 侧映射 将 ExoPlayer 的 MIME 类型翻译为 FFmpeg 解码器名再经ffmpegHasDecoder(codecName)对应 C 侧avcodec_find_decoder_by_name见 ffmpeg_jni.cc校验解码器是否被打包MIME 类型FFmpeg 解码器名备注audio/mp4a-latm(AAC)aacaudio/mpeg/ MPEG-L1 / L2mp3audio/ac3ac3audio/eac3/ E-AC-3 JOCeac3audio/true-hdtruehdaudio/vnd.dts/ DTS-HDdcaaudio/vorbisvorbisaudio/opusopusaudio/3gpp(AMR-NB)amrnbaudio/amr-wbamrwbaudio/flacflacaudio/alacalacaudio/rawpcm_mulaw/pcm_alaw仅当 PCM 编码为 mu-law / A-law 时若解码器未编译进 FFmpegsupportsFormat会输出日志No codec decoder available. Check the FFmpeg build configuration.——这正是排查为什么某个格式没生效的直接线索。因此第 4 节的--enable-decoder...列表必须按需补齐比如要支持 AC-3/DTS就需追加--enable-decoderac3 --enable-decodereac3 --enable-decoderdca等。7.2 渲染器判定流程FfmpegAudioRenderer.supportsFormatInternalsupportsFormatInternal 依次判定FfmpegLibrary.isAvailable()失败 →FORMAT_UNSUPPORTED_TYPEsupportsFormat失败或输出格式不被 AudioSink 支持 →FORMAT_UNSUPPORTED_SUBTYPEDRM 校验失败 →FORMAT_UNSUPPORTED_DRM通过 →FORMAT_HANDLED。渲染器内部使用 16 个输入/输出缓冲区NUM_BUFFERS 16默认输入缓冲区 960×6 字节若Format.maxInputSize已知则优先采用。解码输出统一封装为audio/rawPCM 格式见 getOutputFormat。7.3 32 位浮点输出enableFloatOutput构造器提供enableFloatOutput参数默认false。启用后若设备支持 PCM float 输出shouldUseFloatOutput 会决策audio/raw仅当位深为 24/32-bit 或已是 float 时输出 floataudio/ac3AC-3 恒为 16-bit无 float 价值返回 false其余格式默认输出 float。注意副作用启用 32 位 float 输出后任何音频后处理都会被禁用包括播放速度/音高调节。7.4 解码器封装FfmpegDecoderFfmpegDecoder 负责extra data 组装AAC/Opus 直接取initializationData[0]ALAC 需把 magic cookie 包装成 ALAC atom12 字节头 cookieVorbis 需把两个 header 按长度前缀格式拼接。这是 FFmpeg 解码这些格式所必需的解码器初始化数据。错误码约定-1DECODER_ERROR_INVALID_DATA按非致命错误处理仅将该缓冲区标记为BUFFER_FLAG_DECODE_ONLY与 MediaCodec 行为对齐-2DECODER_ERROR_OTHER则抛出FfmpegDecoderException。ALAC 采样率回填早期 FFmpeg 版本 ALAC 解码器不设置 sample_rate需从 extra data 末尾 4 字节解析。reset 语义C 侧对 TrueHD 采取释放并重建 context策略ffmpeg_jni.cc其余编解码器走avcodec_flush_buffers。7.5 JNI 解码主循环decodePacket 采用现代 FFmpeg 的 send/receive 模型avcodec_send_packet送入压缩数据循环avcodec_receive_frame取出解码帧AVERROR(EAGAIN)表示输出耗尽正常结束用avresample_*把解码后的原生采样格式统一重采样为请求的输出格式PCM 16-bit 或 float并校验重采样后无残留样本。此外JNI_OnLoad中调用avcodec_register_all()注册全部编解码器返回JNI_VERSION_1_6。八、测试与验证扩展模块自带的单元测试 DefaultRenderersFactoryTest.java 通过DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(FfmpegAudioRenderer.class, C.TRACK_TYPE_AUDIO)断言在EXTENSION_RENDERER_MODE_ON下DefaultRenderersFactory能够正确实例化FfmpegAudioRenderer并注册到音频 track 的 renderer 列表中。该测试可复用于验证上述任一接入方式是否生效。九、常见问题与注意事项解码器缺失日志出现No xxx decoder available. Check the FFmpeg build configuration.说明对应--enable-decoderxxx未在COMMON_OPTIONS中启用需重跑第 4 节构建并加入对应 flag。NDK 版本仅支持至 NDK 15c更新的 NDK 移除了 4.9 工具链与gnustl_static会导致Android.mk/Application.mk构建失败。ABI 一致性ndk-build APP_ABI列表必须与./configure --libdirandroid-libs/abi的架构集合完全一致否则运行期System.loadLibrary会因缺.so失败。格式裁剪策略本扩展走软解兜底路线建议仅启用 MediaCodec 覆盖不到且业务需要的格式如 AC-3/DTS/TrueHD以控制 APK 体积与解码功耗。渲染器优先级EXTENSION_RENDERER_MODE_ON与PREFER的取舍取决于你是希望系统硬解优先、FFmpeg 兜底还是FFmpeg 一律优先结合 7.2 节的判定顺序PREFER模式下只要 FFmpeg 声明支持即被选中。【免费下载链接】SmartTubeBrowse media content with your own rules on Android TV项目地址: https://gitcode.com/GitHub_Trending/smar/SmartTube创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表