4. 解码器配置:MIME类型与Codec选择、分辨率与码率适配、色彩格式协商、CSD处理
好,咱们进入正题。解码器配置这块,说白了就是告诉系统:「我要解码什么格式的视频,你给我准备好」。这一步要是没做对,后面跑起来全是坑。我这些年踩过的雷,有一半都跟配置有关。
4.1 MIME类型与Codec选择
先说说MIME类型。这东西就是个标识,告诉系统视频是H.264还是H.265,或者是VP9之类的。Android里用MediaCodec的时候,第一步就是创建解码器,而创建的依据就是这个MIME类型。
核心要点:MIME类型必须与视频流的实际编码格式严格匹配。匹配错了,解码器要么创建失败,要么解码出来的画面全是马赛克。
常见的MIME类型有这么几个:
| 编码格式 | MIME类型 | 备注 |
|---|---|---|
| H.264 / AVC | video/avc | 最常用,兼容性最好 |
| H.265 / HEVC | video/hevc | 高压缩率,但老设备不支持 |
| VP8 | video/x-vnd.on2.vp8 | WebM格式常用 |
| VP9 | video/x-vnd.on2.vp9 | YouTube等平台在用 |
| AV1 | video/av01 | 新一代编码,硬解支持有限 |
我个人习惯是,拿到视频流之后先解析它的编码信息,再动态选择MIME类型。千万别写死。为什么?因为同一个视频文件,不同封装格式可能提取出来的编码信息不一样。我在项目中遇到过,一个MP4文件里明明是H.265,但有的解析库返回的是H.264的标识,结果解码器创建失败,折腾了半天才发现是解析库的bug。
创建解码器的代码大概长这样:
MediaCodec decoder = MediaCodec.createDecoderByType("video/avc");
// 或者通过MediaFormat来创建
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
decoder = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
嗯,这里要注意:createDecoderByType可能会抛出异常,一定要try-catch。有些设备上,某些MIME类型就是不支持的,比如低端机对HEVC的支持就很差。
4.2 分辨率与码率适配
分辨率适配,说白了就是你的解码器能不能扛得住这么大的画面。我见过有人直接把4K视频丢给只支持1080p的解码器,结果画面卡成PPT,还以为是网络问题。
Android里,每个解码器都有它支持的最大分辨率。你可以通过MediaCodecInfo.CodecCapabilities来查询:
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
MediaCodecInfo codecInfo = codecList.findDecoderForFormat(format);
if (codecInfo != null) {
MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mimeType);
// 获取支持的最大分辨率
int maxWidth = caps.getVideoCapabilities().getSupportedWidths().getUpper();
int maxHeight = caps.getVideoCapabilities().getSupportedHeights().getUpper();
}
你想想看,如果视频分辨率超过了解码器的上限,怎么办?我一般会做两件事:
- 降级处理:如果硬解不支持,就退回到软解。虽然慢一点,但至少能播。
- 缩放解码:有些解码器支持直接输出缩放后的画面,通过
KEY_MAX_WIDTH和KEY_MAX_HEIGHT来限制输出尺寸。
码率适配这块,其实更偏向于网络播放的场景。本地播放的话,码率一般不是问题。但如果是直播或者点播,码率太高解码器可能来不及处理,就会出现丢帧或者花屏。
我的经验:对于1080p的视频,码率控制在10Mbps以内,大部分解码器都能轻松应对。4K视频建议不超过40Mbps。超过这个值,很多中低端设备的解码器就开始吃力了。
4.3 色彩格式协商
色彩格式,这是个容易被忽略但特别重要的点。解码器输出的数据是什么颜色排列的?是NV12还是YV12?是RGBA还是BGRA?这些直接影响到你后续的渲染或者处理。
Android里,解码器默认输出的色彩格式通常是COLOR_FormatYUV420SemiPlanar,也就是NV12。但不同厂商的实现可能不一样。我遇到过某款华为手机,解码器输出的是COLOR_FormatYUV420Planar,也就是I420,结果渲染的时候颜色完全不对。
怎么协商?在配置解码器的时候,你可以通过MediaFormat来指定你期望的色彩格式:
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
但说实话,这个设置不一定生效。解码器会根据自己的能力来选择最合适的格式。更靠谱的做法是,解码器启动后,从输出缓冲区里拿到实际的色彩格式信息:
MediaFormat outputFormat = decoder.getOutputFormat();
int colorFormat = outputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
然后根据这个格式来做后续的处理。我一般会写一个色彩格式转换器,支持NV12、I420、NV21之间的互转。这样不管解码器输出什么,我都能处理。
注意:有些解码器输出的YUV数据是跨行存储的(stride不等于width),处理的时候一定要考虑stride,否则画面会出现条纹或者错位。我曾经在这个坑里爬了整整两天。
4.4 CSD(Codec Specific Data)处理
CSD,全称是Codec Specific Data,翻译过来就是「编码器特定数据」。这东西是干嘛的呢?简单说,就是视频流里除了画面数据之外,还有一些描述信息,比如H.264的SPS和PPS,H.265的VPS、SPS和PPS。解码器需要这些信息才能正确解码。
对于H.264来说,CSD通常包含两个部分:
- SPS(Sequence Parameter Set):描述视频的序列参数,比如分辨率、帧率、参考帧数量等。
- PPS(Picture Parameter Set):描述图像的参数,比如熵编码模式、分片数量等。
在Android里,CSD是通过MediaFormat的KEY_CSD-0、KEY_CSD-1等键值来传递的:
byte[] sps = ...; // 从视频流中提取的SPS数据
byte[] pps = ...; // 从视频流中提取的PPS数据
format.setByteBuffer("csd-0", ByteBuffer.wrap(sps));
format.setByteBuffer("csd-1", ByteBuffer.wrap(pps));
这里有个关键点:CSD数据必须在解码器启动之前就设置好。如果你在解码过程中才设置,解码器会重新配置,导致画面出现短暂的黑屏或者花屏。
我曾经犯过一个错误:从网络流中解析视频数据时,SPS和PPS是分开到达的。我等到两个都到齐了才设置CSD,结果中间有几帧数据因为没有CSD信息,解码出来全是绿色的马赛克。后来我改成先缓存视频数据,等CSD到齐了再启动解码器,问题就解决了。
最佳实践:对于H.264,CSD数据通常位于视频流的开头,也就是第一个IDR帧之前。你可以通过解析视频文件的头部信息来获取。对于H.265,CSD包含VPS、SPS、PPS三部分,分别对应csd-0、csd-1、csd-2。
另外,有些封装格式(比如MP4)会把CSD信息放在容器的元数据里,不需要你手动提取。这种情况下,你可以直接用MediaExtractor来获取:
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(filePath);
MediaFormat format = extractor.getTrackFormat(0);
// format里已经包含了csd-0和csd-1
嗯,用MediaExtractor是最省心的方式。但如果你是自己解析视频流(比如从网络直播流中),那就得手动提取CSD了。
4.5 整体配置流程
说了这么多,咱们来捋一下解码器配置的完整流程。我画了个图,方便你理解:
整个流程其实不复杂,但每一步都有细节要注意。我总结一下核心要点:
- 先解析,后配置:不要盲目创建解码器,先搞清楚视频的编码格式、分辨率、CSD信息。
- 能力查询不能省:每个设备的解码能力都不一样,一定要动态查询,不要假设。
- CSD要提前设置:解码器启动前必须把CSD数据准备好,否则解码会出问题。
- 色彩格式要适配:不要硬编码色彩格式,根据解码器实际输出做适配。
好了,解码器配置这块就讲到这里。这些内容看起来琐碎,但每一个细节都关系到最终的播放效果。你把这些搞清楚了,后面解码、渲染的环节就会顺畅很多。
公众号:蓝海资料掘金营,微信deep3321