引言:那些"神秘"的 vendor 参数是怎么来的
用 MediaCodec 开发的时候,偶尔会看到这样的代码:
format.setInteger("vendor.qti-ext-enc-ltr-count.num-ltr-frames", 4);
format.setInteger("vendor.rtc-ext-enc-low-latency.enable", 1);
这些以 vendor. 开头的参数是什么?为什么官方文档里找不到?它们是怎么传递到硬件编码器的?
答案在 OMX 和 Codec2 这两个框架里。它们是 MediaCodec 和硬件之间的"翻译层",也是整个 Android 编解码架构最复杂、最厂商定制化的部分。
理解了这一层,你才能真正读懂 Vendor 的编解码优化文档,才能在不同芯片平台上榨出最优性能,才能处理那些"换了手机就出问题"的兼容性 Bug。
一、两代框架的历史背景
1.1 OMX(OpenMAX IL):第一代
OMX 全称 OpenMAX Integration Layer,是 Khronos Group 制定的多媒体组件标准(类似 OpenGL 之于图形)。Android 从 2.0 开始基于 OMX 构建编解码框架,核心实现是 ACodec(libstagefright)。
OMX 的基本单元是组件(Component),每个编解码器是一个组件,通过 Port 端口传递 Buffer。问题在于:OMX IL 规范设计于 2006 年,面向嵌入式设备,用 C 接口、全局状态、阻塞调用——在 2016 年多核 SoC 的时代显得捉襟见肘。
1.2 Codec2:第二代(Android 10+)
Google 从 Android 9 开始引入 Codec2(内部代号 C2),Android 10 开始强制要求新设备支持。Codec2 彻底重写了 Framework 侧逻辑,用 C++17 模板类替代了 OMX 的 C 接口,引入了现代化的内存模型(C2Buffer/C2Block)和异步工作流(C2Work)。
为什么叫 Codec2 而不是 Codec v2? 因为第一版直接叫"codec"(OMX),第二版就叫 Codec2,表示这是彻底的重新设计,而非小修小补。
二、OMX 框架深入:ACodec 工作原理
2.1 ACodec 状态机
ACodec 是 MediaCodec 在 OMX 通道上的具体实现,也是一个状态机:
Uninitialized
│ allocateComponent()
▼
Loaded
│ configureComponent() + allocateBuffers()
▼
Idle
│ executeComponent()(OMX_CommandStateSet → Executing)
▼
Executing ← 正常运行状态,通过 OMX_EmptyThisBuffer / OMX_FillThisBuffer 传递 Buffer
│
│ OMX_EventPortSettingsChanged(分辨率变化时)
│ → 临时进入 OutputPortSettingsChangedState
│ → 重新分配 Output Buffer
│ → 回到 Executing
│
│ flush() → OMX_CommandFlush
│ stop() → OMX_CommandStateSet Idle → Loaded
2.2 OMX Buffer 传递
OMX 的 Buffer 传递是所有权转移语义:
Framework(ACodec) OMX Component(硬件驱动)
│
│ OMX_UseBuffer():告知 HAL 使用 Framework 分配的 Buffer 地址
│ ──────────────────────────────────────────────────────────►
│ Buffer 所有权在 HAL
│ OMX_EmptyThisBuffer(inputBuffer):把输入 Buffer 填好后交给 HAL
│ ──────────────────────────────────────────────────────────►
│ HAL 处理后回调
│ ◄──────────────────────────────────── EmptyBufferDone()
│ Buffer 所有权回到 Framework
│
│ OMX_FillThisBuffer(outputBuffer):把空的输出 Buffer 交给 HAL 填
│ ──────────────────────────────────────────────────────────►
│ HAL 填完后回调
│ ◄──────────────────────────────────── FillBufferDone()
│ 拿到填好的 Buffer
OMX 的设计缺陷:Buffer 在 Framework 和 HAL 之间频繁"交接",每次所有权转移需要两次函数调用。加上 OMX 接口是同步的(OMX_SetParameter 会阻塞直到 HAL 响应),在高帧率场景下延迟和开销都很明显。
2.3 OMX 扩展参数:vendor.xxx
OMX 规范定义了标准参数(OMX_IndexParamVideoAvc 等),但厂商需要暴露更多硬件能力,于是引入了 Vendor Extension:
// 高通在 OMX HAL 中注册扩展参数
const char* extParam = "OMX.QTI.index.param.video.LTRCount";
OMX_INDEXTYPE ltrIndex;
OMX_GetExtensionIndex(hComp, extParam, <rIndex);
OMX_SetParameter(hComp, ltrIndex, <rStruct);
这些扩展参数通过一个映射表暴露到 MediaFormat,就是我们看到的 vendor.qti-ext-enc-ltr-count.num-ltr-frames。Codec2 中有更规范的 Vendor Parameter 机制(见后文)。
三、Codec2 框架深入:C2Component 接口
3.1 C2Component 核心接口
Codec2 的组件接口比 OMX 简洁得多,核心只有三个操作:
// C2Component 接口(简化)
class C2Component {
public:
// 提交工作单元(输入 + 输出 Buffer 描述)
virtual c2_status_t queue_nb(
std::list<std::unique_ptr<C2Work>>* const items) = 0;
// 刷新:丢弃所有待处理的工作,返回未完成的工作
virtual c2_status_t flush_sm(
flush_mode_t mode,
std::list<std::unique_ptr<C2Work>>* const flushedWork) = 0;
// 向组件发送 drain(结束符),请求输出所有缓存的帧
virtual c2_status_t drain_nb(drain_mode_t mode) = 0;
// 状态控制
virtual c2_status_t start() = 0;
virtual c2_status_t stop() = 0;
virtual c2_status_t release() = 0;
};
3.2 C2Work:工作单元
Codec2 的核心抽象是 C2Work,每个工作单元包含一组输入 Buffer + 输出 Buffer 描述,以及 metadata:
struct C2Work {
// 输入:一个或多个 C2FrameData
std::list<std::unique_ptr<C2StreamBuffer>> input; // 压缩码流 Buffer
// 处理链(通常只有一个节点)
std::vector<std::unique_ptr<C2Worklet>> worklets;
// 完成后的结果(异步回调时由 Component 填充)
// worklets[0].output 包含解码/编码后的 C2Buffer
};
struct C2Worklet {
C2FrameData output; // 输出 Buffer(解码帧/编码码流)
c2_status_t result;
};
3.3 C2Buffer 内存模型
C2Buffer 是 Codec2 最重要的改进之一。它基于引用计数 + 共享内存,解决了 OMX 的所有权混乱问题:
// C2Buffer 的两种底层类型:
// 1. C2LinearBlock:线性内存(压缩码流、音频 PCM)
std::shared_ptr<C2LinearBlock> block;
C2BlockPool::local_id_t poolId = C2BlockPool::BASIC_LINEAR;
blockPool->fetchLinearBlock(
capacity,
{C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE},
&block
);
// 写入数据
C2WriteView wv = block->map().get();
memcpy(wv.data(), srcData, size);
// 创建 C2Buffer(只读视图,可以跨线程/进程共享)
std::shared_ptr<C2Buffer> buffer = C2Buffer::CreateLinearBuffer(
block->share(offset, size, C2Fence())
);
// 2. C2GraphicBlock:图形内存(YUV 帧,GPU 可访问)
std::shared_ptr<C2GraphicBlock> graphicBlock;
blockPool->fetchGraphicBlock(width, height,
HAL_PIXEL_FORMAT_YCbCr_420_888,
{C2MemoryUsage::CPU_READ, C2MemoryUsage::HW_CODEC_WRITE},
&graphicBlock
);
C2Fence:异步同步机制
C2Buffer 配套 C2Fence,功能类似 Android sync fence,但集成在 Codec2 框架内:
// 等待 fence 信号后才能访问 Buffer
C2Fence fence = buffer->fence();
if (fence.valid()) {
c2_status_t err = fence.wait(timeoutNs);
// err == C2_OK:Buffer 已准备好
// err == C2_TIMED_OUT:超时
}
3.4 C2ComponentStore:组件注册
Codec2 通过 C2ComponentStore 管理所有可用的编解码器。系统启动时,MediaCodec Service 会加载所有已注册的 Store:
// 获取所有已注册的 C2ComponentStore(代码路径:libmediacodec2)
std::vector<std::shared_ptr<const C2Component::Traits>> traits;
store->listComponents(&traits);
for (auto& t : traits) {
ALOGD("Codec: %s, media_type: %s, rank: %u",
t->name.c_str(), // "c2.qti.avc.encoder"
t->mediaType.c_str(), // "video/avc"
t->rank); // 排名越低越优先选择(HW < SW)
}
Rank 决定优先级:同一 MIME 类型可能有多个组件(HW encoder、SW encoder),rank 值越低优先级越高。硬件编码器 rank 通常是 64,软件备用的 OMX.google.* rank 是 0x7fffffff(最低优先级)。
四、硬件视频编码:参数精讲
4.1 码率控制三模式
这是编码参数中最重要的选择,直接影响文件大小和画质:
// CBR(Constant Bit Rate)— 恒定码率
// 适合:直播推流、实时通话(码率稳定,网络友好)
// 缺点:静止画面浪费带宽,运动画面质量不足
format.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000); // 4Mbps
// VBR(Variable Bit Rate)— 可变码率
// 适合:本地录像(平均码率可控,画质更均匀)
// 实际码率在目标码率上下浮动,运动场景码率高,静止场景低
format.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
// CQ(Constant Quality)— 恒定质量
// 适合:离线转码、截图录制(不控码率,只保证质量)
// 码率完全由画面复杂度决定,无法预测文件大小
format.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);
format.setInteger(MediaFormat.KEY_QUALITY, 80); // 质量 0~100
4.2 LTR(Long-Term Reference)帧:实时通话的关键
LTR 是高通等芯片专门为视频通话设计的编码优化,允许编码器在丢包后快速恢复:
// 启用 LTR(需要高通芯片支持)
// 设置 LTR 帧数量
format.setInteger("vendor.qti-ext-enc-ltr-count.num-ltr-frames", 2);
// 标记某帧为 LTR 参考帧(动态控制)
Bundle params = new Bundle();
params.putInt("vendor.qti-ext-enc-ltr.mark-frame", 1);
encoder.setParameters(params);
// 强制使用 LTR 帧作为参考(在检测到丢包后)
params.putInt("vendor.qti-ext-enc-ltr.use-frame", 1);
encoder.setParameters(params);
4.3 动态码率调整
实时推流时根据网络状况动态调整码率是标配能力:
// 运行时动态调整码率(无需重新 configure)
Bundle bitrateParams = new Bundle();
bitrateParams.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate);
encoder.setParameters(bitrateParams);
// 立即请求关键帧(在网络抖动后触发 IDR 帧,接收端快速恢复)
Bundle idrParams = new Bundle();
idrParams.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
encoder.setParameters(idrParams);
4.4 查询硬件编码器真实能力
不同设备的硬件能力差异极大,运行时查询是正确选参数的唯一方法:
private void printEncoderCapabilities(String mimeType) {
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : codecList.getCodecInfos()) {
if (!info.isEncoder()) continue;
boolean supportsMime = false;
for (String type : info.getSupportedTypes()) {
if (type.equalsIgnoreCase(mimeType)) { supportsMime = true; break; }
}
if (!supportsMime) continue;
MediaCodecInfo.CodecCapabilities caps =
info.getCapabilitiesForType(mimeType);
MediaCodecInfo.VideoCapabilities vc = caps.getVideoCapabilities();
MediaCodecInfo.EncoderCapabilities ec = caps.getEncoderCapabilities();
Log.d(TAG, "=== " + info.getName() + " ===");
Log.d(TAG, "硬件加速: " + info.isHardwareAccelerated());
Log.d(TAG, "支持宽度范围: " + vc.getSupportedWidths());
Log.d(TAG, "支持高度范围: " + vc.getSupportedHeights());
Log.d(TAG, "支持帧率范围: " + vc.getSupportedFrameRates());
Log.d(TAG, "支持码率范围: " + vc.getBitrateRange());
Log.d(TAG, "支持 CBR: " + ec.isBitrateModeSupported(BITRATE_MODE_CBR));
Log.d(TAG, "支持 VBR: " + ec.isBitrateModeSupported(BITRATE_MODE_VBR));
Log.d(TAG, "支持 CQ: " + ec.isBitrateModeSupported(BITRATE_MODE_CQ));
// 检查 4K 30fps 是否支持
Log.d(TAG, "支持 4K@30fps: " +
vc.areSizeAndRateSupported(3840, 2160, 30));
// 检查 1080p 60fps 是否支持
Log.d(TAG, "支持 1080p@60fps: " +
vc.areSizeAndRateSupported(1920, 1080, 60));
// Profile & Level
for (MediaCodecInfo.CodecProfileLevel pl : caps.profileLevels) {
Log.d(TAG, "Profile: " + pl.profile + " Level: " + pl.level);
}
}
}
五、硬件视频解码:Tunneled 模式
5.1 普通解码 vs Tunneled 解码
普通解码路径:VPU → Gralloc Buffer → BufferQueue → SurfaceFlinger → Display
数据经过 BufferQueue,App 可以干预每一帧(如做字幕合成、截图),但涉及多次内存操作。
Tunneled 解码:VPU → (隧道)→ Display 硬件直连
VPU 解码完成后直接通过内部总线送到显示控制器,完全绕过 SurfaceFlinger,适合视频播放器追求极低延迟和功耗的场景(如 DRM 保护内容、4K HDR 播放):
// 检查是否支持 Tunneled 解码
MediaCodecInfo.CodecCapabilities caps =
codecInfo.getCapabilitiesForType("video/hevc");
boolean supportsTunneled = caps.isFeatureSupported(
MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback);
if (supportsTunneled) {
// 获取 AudioTrack 的 AudioSession ID(音视频同步用)
int audioSessionId = audioTrack.getAudioSessionId();
MediaFormat format = MediaFormat.createVideoFormat("video/hevc", width, height);
// 启用 Tunneled 模式
format.setFeatureEnabled(
MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback, true);
format.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
// Tunneled 解码器配置时 Surface 传 null(输出直接到 Display)
decoder.configure(format, null, null, 0);
decoder.start();
}
5.2 Tunneled 模式的限制
Tunneled 模式有几个关键限制需要注意:
- 无法截帧:因为数据不经过 BufferQueue,
ImageReader读不到解码帧 - 无法软件合成字幕:字幕必须通过硬件 OSD 叠加,或使用
MediaSync+AudioTrack同步方案 - DRM 限制:使用 Tunneled 解 DRM 内容时,Gralloc Buffer 通常被标记为
PROTECTED,截图/录屏会得到黑屏 - 不是所有设备支持:低端设备可能没有这条硬件路径
5.3 低延迟解码(直播场景)
// Android 11+ 低延迟解码模式
format.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
// 配合零帧延迟(不缓冲任何帧)
format.setInteger("vendor.qti-ext-dec-output-delay.output-delay", 0);
// Android 12+ 更精细的延迟控制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
format.setInteger(MediaFormat.KEY_LATENCY, 0); // 0 = 最低延迟
}
六、AV1 硬件编解码:Android 15 的重点
6.1 AV1 的崛起
AV1 是 Alliance for Open Media 推出的新一代开放视频编码标准,相比 H.265 同等画质下码率再降约 30%,且完全免专利费。YouTube、Netflix 已大量使用 AV1,Android 15 加强了 AV1 硬件支持。
Android 15 AV1 支持现状:
- 高通 Snapdragon 8 Gen 2+:支持硬件 AV1 编码 + 解码
- MediaTek Dimensity 9000+:支持硬件 AV1 解码(编码仍部分依赖软件)
- Google Tensor G3:支持 AV1 硬件编解码
- 低端设备:软件 AV1 解码(
c2.android.av1.decoder,性能较差)
6.2 AV1 编解码代码示例
// 检查设备是否支持硬件 AV1 编码
public static boolean supportsHardwareAv1Encoding() {
MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo info : list.getCodecInfos()) {
if (!info.isEncoder() || !info.isHardwareAccelerated()) continue;
for (String type : info.getSupportedTypes()) {
if (type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) return true;
}
}
return false;
}
// AV1 编码配置
MediaFormat av1Format = MediaFormat.createVideoFormat(
MediaFormat.MIMETYPE_VIDEO_AV1, 1920, 1080);
av1Format.setInteger(MediaFormat.KEY_BIT_RATE, 3_000_000); // AV1 同等质量码率更低
av1Format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
av1Format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
av1Format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
// AV1 Profile(Android 13+)
av1Format.setInteger(MediaFormat.KEY_PROFILE,
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8); // 8-bit Main Profile
MediaCodec av1Encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AV1);
av1Encoder.configure(av1Format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
七、Vendor 扩展参数:完整使用指南
7.1 Codec2 的 Vendor Parameter 机制
Codec2 提供了类型安全的 Vendor Parameter 系统,比 OMX 的字符串扩展更规范:
// Vendor 在 HAL 侧定义参数(C++)
// 使用 C2Param 模板类
typedef C2PortParam<C2Info, C2Int32Value,
kParamIndexVendorLowLatency + C2Param::TYPE_DEFAULT> C2VendorLowLatencyTuning;
// 向 Component 设置参数
std::vector<std::unique_ptr<C2SettingResult>> failures;
component->config_vb(
{C2VendorLowLatencyTuning::output(true)}, // 启用低延迟输出
C2_MAY_BLOCK,
&failures
);
在 MediaFormat 中使用时,Codec2 框架自动做映射:
// MediaFormat 键名格式:
// "vendor.<vendor-name>-ext-<param-name>.<field-name>"
// 高通扩展参数(常用)
format.setInteger("vendor.qti-ext-enc-low-latency.enable", 1); // 低延迟编码
format.setInteger("vendor.qti-ext-enc-initial-qp.enable", 1); // 手动初始 QP
format.setInteger("vendor.qti-ext-enc-initial-qp.qp-i", 30); // I帧 QP 值
format.setInteger("vendor.qti-ext-enc-ltr-count.num-ltr-frames", 2); // LTR 帧数
format.setInteger("vendor.qti-ext-enc-intra-refresh.enable", 1); // Intra 刷新(抗丢包)
format.setInteger("vendor.qti-ext-enc-intra-refresh.ir-mbs", 0); // Intra 宏块数
// MTK 扩展参数
format.setInteger("vendor.mtk-ext-enc-scenario.scenario", 1); // 录像场景优化
// 注意:vendor 参数与芯片强绑定,跨平台代码必须先检查支持性!
7.2 如何发现可用的 Vendor 参数
# 方法一:adb dumpsys
adb shell dumpsys media.codec | grep -i vendor
# 方法二:读取 MediaCodecList 的 supportedFeatures
# 代码层面:
MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mimeType);
// Vendor 扩展通过 CodecCapabilities.defaultFormat 暴露
MediaFormat defaults = caps.defaultFormat;
// 遍历所有 key(反射,调试用途)
java.util.Set<String> keys = defaults.getKeys();
for (String key : keys) {
if (key.startsWith("vendor.")) {
Log.d(TAG, "Vendor param: " + key);
}
}
7.3 安全使用 Vendor 参数的最佳实践
// 方法:先检查参数是否存在再设置,避免崩溃
private void setVendorParamSafely(MediaFormat format, String key, int value) {
try {
// Android 12+ 可以检查参数是否受支持
MediaCodecInfo.CodecCapabilities caps =
getCapabilitiesForType(mimeType);
MediaFormat defaultFormat = caps.defaultFormat;
if (defaultFormat.containsKey(key)) {
format.setInteger(key, value);
} else {
Log.w(TAG, "Vendor param not supported on this device: " + key);
}
} catch (Exception e) {
Log.w(TAG, "Failed to set vendor param: " + key, e);
}
}
八、性能优化:五个关键点
8.1 选对编解码器名称
默认 createEncoderByType() 选 rank 最低(优先级最高)的,通常是硬件。但某些场景需要精确控制:
// 精确选择高通的 HEVC 编码器(避免被软件编码器抢占)
private String findHardwareEncoderName(String mimeType) {
MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo info : list.getCodecInfos()) {
if (!info.isEncoder() || !info.isHardwareAccelerated()) continue;
for (String type : info.getSupportedTypes()) {
if (type.equalsIgnoreCase(mimeType)) return info.getName();
}
}
return null; // 没有硬件编码器,回退到软件
}
String encoderName = findHardwareEncoderName(MediaFormat.MIMETYPE_VIDEO_HEVC);
MediaCodec encoder = encoderName != null
? MediaCodec.createByCodecName(encoderName)
: MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC);
8.2 避免格式降级
配置了硬件编码器不支持的参数组合,会触发格式降级(fallback to SW),性能暴降:
// 配置前先验证参数组合
MediaCodecInfo.VideoCapabilities vc =
codecInfo.getCapabilitiesForType(mimeType).getVideoCapabilities();
// 检查分辨率+帧率组合是否支持
if (!vc.areSizeAndRateSupported(width, height, frameRate)) {
// 降低帧率到支持范围
frameRate = vc.getSupportedFrameRatesFor(width, height)
.getUpper().intValue();
}
// 检查码率是否在范围内
Range<Integer> bitrateRange = vc.getBitrateRange();
bitrate = Math.max(bitrateRange.getLower(),
Math.min(bitrateRange.getUpper(), bitrate));
8.3 B 帧控制
B 帧可以提升压缩率约 20%,但引入额外延迟(B 帧需要"看未来帧"才能编码)。实时场景禁用 B 帧:
// 禁用 B 帧(实时通话、直播推流)
// Android 标准参数(Android 12+)
format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0);
// 高通 Vendor 参数(兼容旧版本)
format.setInteger("vendor.qti-ext-enc-num-b-frames.value", 0);
8.4 Codec Performance Mode
Android 12 引入了 KEY_OPERATING_RATE 和 KEY_PRIORITY:
// 告知编解码器期望的运行帧率(帮助 HW 调整内部频率)
format.setInteger(MediaFormat.KEY_OPERATING_RATE, 30); // 目标 30fps
// 优先级:0 = 实时(低延迟),1 = 尽力而为(低功耗)
format.setInteger(MediaFormat.KEY_PRIORITY, 0); // 实时优先
8.5 Profile/Level 的正确选择
Profile 和 Level 决定了硬件可以使用哪些编码工具,选错会导致兼容性问题:
H.264 Profile 选择指南:
Baseline Profile → 最大兼容性(老设备、WebRTC)
Main Profile → 支持 B 帧,兼容性好(录像常用)
High Profile → 支持更多压缩工具,文件最小(本地录像推荐)
H.264 Level 选择指南(常用):
Level 3.1 → 720p@30fps(主流低端设备支持)
Level 4.0 → 1080p@30fps
Level 4.1 → 1080p@30fps + 更高码率
Level 5.0 → 4K@30fps
Level 5.1 → 4K@60fps(高端设备)
九、调试:从 Codec 层定位问题
9.1 查看 Codec2 组件状态
# 查看所有注册的 Codec2 组件
adb shell dumpsys media.codec
# 关键字段解读:
# "Codec2 HAL service availability" → 是否有 Codec2 HAL 可用
# "Components:" → 所有组件列表,含 rank
# "Media codec list:" → MediaCodecList 暴露给 App 的列表
# 实时查看 CCodec 运行状态
adb logcat -s CCodec:V CCodecBuffers:V Codec2Client:V
# 查看 OMX 组件(旧路径)
adb logcat -s OMXNodeInstance:V ACodec:V
9.2 判断走的是 OMX 还是 Codec2
# 通过 logcat 判断:
adb logcat -s MediaCodec | grep -E "CCodec|ACodec"
# 看到 "CCodec" → 走 Codec2 路径
# 看到 "ACodec" → 走 OMX 路径
# 通过代码判断(Android 10+):
// MediaCodecInfo.isHardwareAccelerated() 不等于走 Codec2
// 检查 vendor 组件名前缀来判断
String name = encoder.getName();
boolean isCodec2 = name.startsWith("c2.");
boolean isOMX = name.startsWith("OMX.");
9.3 常见问题排查
问题:编码帧率达不到目标值
# 1. 检查是否是 Thermal 降频
adb shell dumpsys thermalservice | grep -i throttle
# 2. 检查 VPU 频率
adb shell cat /sys/class/devfreq/*/cur_freq # 平台相关路径
# 3. 查看 Codec2 是否报 C2_TIMED_OUT
adb logcat -s CCodec | grep -i "timeout\|timed out\|TIMED_OUT"
问题:切换码率后编码质量突变
# 某些硬件编码器切换码率时需要一个 I 帧作为"基准重置"
# 解决方案:切换码率后立即请求一个关键帧
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate);
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
encoder.setParameters(params); // 两个参数合并在一次 setParameters 调用中
问题:特定分辨率下编码崩溃(SIGSEGV)
// 某些 VPU 要求宽高对齐(通常 16 或 32)
int alignedWidth = (width + 15) & ~15; // 16 对齐
int alignedHeight = (height + 15) & ~15;
// 如果原始尺寸不对齐,需要在编码前 padding
总结
OMX 和 Codec2 是 Android 编解码的两代框架,它们的关系就像 Camera1 和 Camera2:Codec2 以更现代的设计解决了 OMX 的所有痛点,但理解 OMX 仍然有价值,因为大量旧设备和旧代码仍在使用它。
本文核心要点:
- OMX(ACodec):C 接口、同步调用、Port 所有权显式转移,Android 9 及以前主流,现已逐步淡出
- Codec2(CCodec):C++17 模板、
C2Work异步工作单元、C2Buffer引用计数内存模型,Android 10+ 主流 - C2Buffer 两种形态:
C2LinearBlock(线性内存,用于压缩码流)和C2GraphicBlock(图形内存,用于 YUV 帧),配合C2Fence实现无锁异步访问 - 码率控制三模式:CBR(直播)/ VBR(录像)/ CQ(离线转码),选错模式是最常见的编码质量问题根源
- Tunneled 解码:VPU 直连 Display,绕过 SurfaceFlinger,适合 DRM 内容和 4K HDR 播放,但无法截帧
- Vendor 扩展参数:
vendor.xxx前缀,与芯片强绑定,使用前必须检查设备支持性 - AV1 硬件支持:8 Gen 2+、Tensor G3 等旗舰芯片支持硬件 AV1 编解码,相比 H.265 同质量码率再降 ~30%
- 调试手段:
dumpsys media.codec看组件列表,logcat CCodec/ACodec判断路径,Vendor 参数崩溃时先做格式合法性检查
下一篇进入播放器架构:MediaPlayer 与 NuPlayer——从数据源到渲染,把播放一个视频的完整链路解剖清楚。