Android MediaCodec 架构与实现解析
文档时间: 2026-03
本文梳理 Android MediaCodec 的代码位置、分层架构、数据流与线程模型,并说明软解/硬解路径、与 FFmpeg 的关系以及核心 C++ 调用链,便于查阅与二次开发。
目录
- 一、代码位置与仓库
- [二、MediaCodec 架构全景](#二、MediaCodec 架构全景)
- [三、Java API 层](#三、Java API 层)
- [四、JNI 层](#四、JNI 层)
- [五、C++ Native 与编解码器信息层](#五、C++ Native 与编解码器信息层)
- 六、设计模式与数据流
- [七、MediaCodec 是否使用 FFmpeg](#七、MediaCodec 是否使用 FFmpeg)
- [八、软解与硬解是否都走 C++](#八、软解与硬解是否都走 C++)(含内存路径差异、编解码器发现)
- 九、总结与延伸阅读
一、代码位置与仓库
MediaCodec 是 Android 媒体框架的核心,代码分布在 platform_frameworks_base 与 frameworks/av 等仓库。
| 层级 | 路径(frameworks_base) | 说明 |
|---|---|---|
| Java API | media/java/android/media/MediaCodec.java |
应用层调用的主 API(约 6300+ 行) |
media/java/android/media/MediaCodecList.java |
列出可用编解码器 | |
media/java/android/media/MediaCodecInfo.java |
编解码器元数据与能力 | |
| JNI | media/jni/android_media_MediaCodec.cpp |
MediaCodec 的 JNI 实现(约 4400+ 行) |
media/jni/android_media_MediaCodecList.cpp |
编解码器列表的 JNI | |
| C++ 实现 | 主要在 frameworks/av/media 仓库 | libstagefright、Codec2.0、OMX、软件编解码库等 |
二、MediaCodec 架构全景
MediaCodec 采用四层架构:Java API → JNI → C++ Native → HAL/硬件。
┌─────────────────────────────────────┐
│ Java API Layer │ 应用接口
│ MediaCodec.java │
├─────────────────────────────────────┤
│ JNI Layer │ 跨语言桥接
│ android_media_MediaCodec.cpp │
├─────────────────────────────────────┤
│ C++ Native Layer │ 核心逻辑与编解码
│ libstagefright, CodecBase, ACodec, C2Codec
├─────────────────────────────────────┤
│ HAL / Hardware Layer │ 硬件抽象
│ OMX IL, Codec2.0 HAL, V4L2, 厂商 VPU/DSP │
└─────────────────────────────────────┘
说明 :部分平台还会通过 V4L2 等接口访问硬件编解码能力;C++ 实现主要在 frameworks/av 仓库(如 libstagefright、codecs/),与 frameworks_base 的 JNI 配合使用。
三、Java API 层
3.1 生命周期与创建方式
| 阶段 | 方法/说明 |
|---|---|
| 创建 | createDecoderByType() / createEncoderByType() 按 MIME 创建;createByCodecName() 按名称创建;createByCodecNameForClient() 为指定客户端创建 |
| 配置 | configure(MediaFormat, Surface, MediaCrypto, flags) --- 分辨率、码率、Surface、DRM 等 |
| 状态 | start() → stop() / flush() / reset() → release() |
状态机:Uninitialized → Configured → Executing;flush 回到 Executing,stop/reset 回到 Uninitialized。下图概括允许的转换关系:
configure()
Uninitialized ──────────────────► Configured
▲ │
│ │ start()
│ reset() / release() ▼
│ ┌───────────┐
└────────────────────────────│ Executing │◄──── flush()
└───────────┘
│
stop() / release()
典型调用顺序 :创建(如 createDecoderByType("video/avc"))→ configure(MediaFormat, Surface, ...) → start() → 循环 dequeueInputBuffer / queueInputBuffer 与 dequeueOutputBuffer / releaseOutputBuffer(或使用 setCallback 异步处理)→ stop() / release()。
3.2 数据交换模型
| 模式 | 说明 |
|---|---|
| Legacy | dequeueInputBuffer() → 填充数据 → queueInputBuffer();dequeueOutputBuffer() → 处理 → releaseOutputBuffer() |
| Block(现代) | 使用 LinearBlock,支持零拷贝与异步;LinearBlock.obtain() → queueLinearBlock() |
| Surface | 编码:createInputSurface() 从 Surface 取输入;解码:setOutputSurface() 直接输出到 Surface |
3.3 异步回调
MediaCodec.Callback 接口:
onInputBufferAvailable(MediaCodec, int index)onOutputBufferAvailable(MediaCodec, int index, BufferInfo info)onOutputFormatChanged(MediaCodec, MediaFormat format)onError(MediaCodec, CodecException e)
内部通过 EventHandler 在指定线程分发事件。使用 setCallback(Callback, Handler) 可指定回调所在线程(不传 Handler 则使用 Codec 内部线程)。
Callback 时序(解码为例)
异步模式下,回调的典型触发顺序与时机如下(解码一路数据时):
start() 之后
│
▼
onInputBufferAvailable(index) ← 有可用输入 buffer,可往里填数据
│
│ app 调用 queueInputBuffer(index, ...)
▼
(可能多次)onInputBufferAvailable / queueInputBuffer 交替
│
│ 当有解码结果时
▼
onOutputFormatChanged(format) ← 首次有输出前或格式变化时(如分辨率变更)
│
▼
onOutputBufferAvailable(index, BufferInfo) ← 有一帧解码结果可消费
│
│ app 处理完后 releaseOutputBuffer(index, render)
▼
(循环)onOutputBufferAvailable / releaseOutputBuffer
│
│ 若 queueInputBuffer(..., BUFFER_FLAG_END_OF_STREAM)
▼
最后若干次 onOutputBufferAvailable,其中 BufferInfo.flags 含 EOS
│
▼
不再有新的 output,解码结束
| 回调 | 触发时机 |
|---|---|
| onInputBufferAvailable | 有空的输入 buffer 可填充;每次 releaseInputBuffer 或 Codec 内部归还 buffer 后可能触发 |
| onOutputFormatChanged | 输出格式确定或发生变化(如首帧解码出、分辨率/色彩格式变更),应在使用输出 buffer 前处理新 format |
| onOutputBufferAvailable | 有一帧解码/编码结果可读,需在合适时机 releaseOutputBuffer(index, render) 释放 |
| onError | 编解码错误、配置错误等,Codec 进入错误状态,通常需 release() 后重新创建 |
注意 :回调与 dequeue* 互斥,使用 Callback 时不要混用同步的 dequeueInputBuffer/dequeueOutputBuffer。
3.4 加密与常量
CryptoInfo :封装 DRM 信息(加密模式、子样本、IV、密钥 ID);通过 queueSecureInputBuffer() 提交加密数据。
Buffer 标志与返回值(常用常量速查):
| 类型 | 常量 | 值 | 含义 |
|---|---|---|---|
| Buffer 标志 | BUFFER_FLAG_SYNC_FRAME |
1 | 关键帧 |
BUFFER_FLAG_CODEC_CONFIG |
2 | 编解码器配置数据 | |
BUFFER_FLAG_END_OF_STREAM |
4 | 流结束 | |
| dequeue 返回值 | INFO_TRY_AGAIN_LATER |
-1 | 暂无可用缓冲 |
INFO_OUTPUT_FORMAT_CHANGED |
-2 | 输出格式改变 | |
INFO_OUTPUT_BUFFERS_CHANGED |
-3 | 输出缓冲区改变(旧版 API) |
四、JNI 层
JNI 层是 Java 与 C++ 的桥接,负责类型转换、异常映射与 Native 句柄管理。
| 维度 | 说明 |
|---|---|
| 方法映射 | JNINativeMethod 表将 Java 的 native_* 方法映射到 C++ 实现(如 native_configure → android_media_MediaCodec_native_configure) |
| 上下文 | Java 侧 mNativeContext 持有 C++ JMediaCodec;通过 getMediaCodec() / setMediaCodec() 存取 |
| 类型转换 | ByteBuffer ↔ AByteBuffer;Surface ↔ ANativeWindow;MediaFormat ↔ AMessage;CryptoInfo ↔ CodecCryptoInfo |
| 错误 | throwExceptionAsNecessary() 将 C++ status_t 转为 Java CodecException / CryptoException |
| 扩展 | HardwareBuffer 映射、LinearBlock 零拷贝、Vendor 参数订阅与查询 |
五、C++ Native 与编解码器信息层
5.1 C++ 核心结构(frameworks/av)
| 类/组件 | 说明 |
|---|---|
| MediaCodec | 统一编解码管理,状态机与消息驱动 |
| JMediaCodec | JNI 适配,供 android_media_MediaCodec 调用 |
| CodecBase | 编解码器基类 |
| ACodec | OMX IL 实现 |
| C2Codec | Codec2.0 实现 |
| MediaCodecBuffer | 缓冲区抽象 |
| ALooper / AMessage / AHandler | 事件循环与异步消息 |
5.2 MediaCodecInfo(编解码器元数据)
- MediaCodecInfo :名称、规范名、标志(编码/解码、软/硬);
isEncoder()、isSoftwareOnly()、isHardwareAccelerated()。 - CodecCapabilities:AudioCapabilities、VideoCapabilities、EncoderCapabilities、CodecProfileLevel 等。
- Feature :
isFeatureSupported(String feature),如 Adaptive Playback、Secure Playback、Tunneled Playback。
六、设计模式与数据流
6.1 设计模式
| 模式 | 体现 |
|---|---|
| 工厂 | createDecoderByType("video/avc")、createByCodecName("c2.android.avc.decoder") |
| 策略 | 不同后端(OMX vs Codec2.0)通过统一接口暴露 |
| 观察者 | Callback 异步事件通知 |
| 桥接 | JNI 层连接 Java 与 C++ |
6.2 解码/编码数据流
解码管线(从输入到输出):
应用层 JNI C++ / HAL
┌─────────┐ ┌─────────────┐ ┌──────────────────┐
│ 输入数据 │──queue────►│ native_* │────────►│ Codec 解码 │
│ (或 Surface) │ (ByteBuffer/│ │ (软解/硬解) │
└─────────┘ │ Surface) │ └────────┬─────────┘
└─────────────┘ │
▼
┌─────────┐ ┌─────────────┐ ┌──────────────────┐
│ Surface │◄──release──│ dequeue │◄────────│ 解码后 Buffer │
│ 或 YUV │ Output │ OutputBuffer│ │ (或直接到 Surface) │
└─────────┘ └─────────────┘ └──────────────────┘
编码管线 :原始帧(ByteBuffer 或 createInputSurface())→ queueInputBuffer() / Surface → Native Codec → 编码完成 → dequeueOutputBuffer() 取码流。
| 方向 | 输入入口 | 输出出口 |
|---|---|---|
| 解码 | queueInputBuffer() 或 Surface(若用 setOutputSurface()) |
dequeueOutputBuffer() → Surface / ByteBuffer |
| 编码 | queueInputBuffer() 或 createInputSurface() |
dequeueOutputBuffer() → 编码数据 |
6.3 线程模型
| 线程 | 职责 |
|---|---|
| 应用线程 | 调用 API |
| 回调线程 | 执行 Callback(可自定义 Handler) |
| Native 工作线程 | ALooper,处理编解码任务 |
| HAL 线程 | 硬件抽象层 |
6.4 性能与扩展
- 零拷贝:LinearBlock、HardwareBuffer;Surface 直连减少拷贝。
- 多实例与资源 :通过
getRequiredResources()查询编解码器所需资源,便于多实例场景下的资源管理。 - 扩展:Vendor 参数、Crypto 插件、自定义 Codec2 组件。
七、MediaCodec 是否使用 FFmpeg
结论:MediaCodec 不使用 FFmpeg。
7.1 实际实现路径
| 路径 | 实现方式 | 说明 |
|---|---|---|
| 硬件编解码 | 芯片厂商通过 OMX IL / Codec2.0 HAL | 高通、联发科、三星、海思等;直接使用 VPU/DSP/GPU,不依赖 FFmpeg |
| 软件编解码 | 系统自带 C++ 库 | 位于 frameworks/av/media/libstagefright/codecs/ 等 |
7.2 系统软解使用的库
| 编解码器 | 库 | 说明 |
|---|---|---|
| H.264/AVC | libavc | 基于 OpenMAX 等 |
| H.265/HEVC | libhevc | 系统/开源实现 |
| VP8/VP9 | libvpx | WebM 项目 |
| AV1 | libaom | AOMedia |
| AAC | FAAD2 或自研 | 音频解码 |
| MP3 | 自研 | 避免专利与依赖 |
特点:专注编解码、无封装/网络等,便于集成 Codec2.0 与优化。
7.3 为何不用 FFmpeg
| 原因 | 说明 |
|---|---|
| 许可 | LGPL/GPL,设备厂商常需更宽松策略 |
| 架构 | FFmpeg 是完整工具链;MediaCodec 只需编解码,且需适配 OMX/Codec2.0 |
| 性能与控制 | 通用优化与厂商硬件路径不同;Google 需完全可控的编解码栈 |
7.4 若需使用 FFmpeg
- NDK 自集成:JNI 调用自编 C++,内部使用 FFmpeg API。
- 封装为 Codec2 组件:将 FFmpeg 封装成 C2 组件,经 MediaCodec 调用(实现复杂)。
- ExoPlayer + FFmpeg 扩展:多用于格式解析等,而非替代 MediaCodec 编解码。
7.5 查看设备编解码器
java
MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
String name = codecInfo.getName();
boolean isSoftware = codecInfo.isSoftwareOnly();
boolean isHardware = codecInfo.isHardwareAccelerated();
// c2.android.* 多为软解,OMX.xxx.* 多为厂商硬解
}
八、软解与硬解是否都走 C++
结论:是。软解和硬解最终都在 C++ 中执行;Java 层仅为 API 包装。
8.1 软件解码调用链
Java MediaCodec
→ JNI (android_media_MediaCodec.cpp)
→ MediaCodec (libstagefright)
→ C2Component / OMX 组件(软件)
→ libhevc.so / libvpx.so / libavc.so / libaac.so
→ CPU 执行解码(C++/Assembly)
8.2 硬件解码调用链
Java MediaCodec
→ JNI (android_media_MediaCodec.cpp)
→ MediaCodec (libstagefright)
→ ACodec (OMX) 或 C2Codec (Codec2.0)
→ OMX IL HAL / Codec2.0 HAL
→ IPC (Binder/HIDL) → 厂商 HAL (C++)
→ 驱动 → VPU/DSP/专用电路
8.3 对比小结
| 层级 | 软件解码 | 硬件解码 |
|---|---|---|
| Java | MediaCodec.java | MediaCodec.java |
| JNI | android_media_MediaCodec.cpp | 同上 |
| 框架 C++ | C2Codec / ACodec | C2Codec / ACodec |
| 解码实现 | libhevc、libvpx、libavc 等 | 厂商 HAL |
| 执行单元 | CPU | VPU/DSP/专用电路 |
| 是否 FFmpeg | 否 | 否 |
8.4 为何全是 C++
- 性能:软解需密集运算,C++/Assembly 才能实时;硬解需直接操作硬件与驱动。
- 跨平台与集成:C++ 可编译到多架构,厂商易于提供 HAL 实现。
- 历史与标准:Android 媒体栈与 OpenMAX 均为 C/C++。
- 安全与隔离:Native 层独立内存与权限控制(如 SELinux)。
8.5 软解与硬解的内存路径差异
| 路径 | 流程概要 |
|---|---|
| 软解 | 在 C++ 侧分配内存 → 解码到软件 Buffer(C++)→ 经 JNI 传回 Java(零拷贝或复制) |
| 硬解 | 分配硬件 Buffer(C++ + 驱动)→ 解码到硬件 Buffer(DMA 等)→ 经 Surface 渲染(零拷贝,通常不经过 Java 层) |
硬解路径下,解码结果多在 GPU/VPU 缓冲区内直接用于渲染,减少 CPU 与 Java 层参与。
8.6 编解码器如何被发现
MediaCodecList 通过 native 层枚举系统注册的编解码器:系统软件解码器 (如 c2.android.avc.decoder)与厂商硬件解码器 (如 OMX.qcom.video.decoder.avc)。每个编解码器在底层对应一个 C++ 组件实例,通过 JNI 暴露名称与能力给 Java。
九、总结与延伸阅读
- 架构:四层(Java API → JNI → C++ Native → HAL/硬件);状态机与 ALooper/AMessage 驱动。
- 数据:Legacy/Block/Surface 三种模式;Callback 异步;零拷贝与 Surface 直连优化。
- FFmpeg:官方 MediaCodec 软硬解均不依赖 FFmpeg;若需 FFmpeg 需自行通过 NDK 或 C2 封装集成。
- 软硬解:均经 JNI 进入 C++,软解走 libhevc/libvpx 等,硬解走 OMX/Codec2 HAL 到厂商 VPU/DSP。
延伸阅读:
- Android 官方 MediaCodec 文档
- frameworks/av 下 libstagefright、codecs、OMX、Codec2 源码
- OpenMAX IL、Codec2.0 规范与 HAL 接口