11、多媒体框架移植:GStreamer与Codec2框架、视频编解码器(VPU/GPU)驱动、音频ALSA架构适配、Camera HAL与ISP调试
多媒体这块,说实在的,是车载系统里最折腾人的部分之一。你想想看,用户坐进车里,第一件事可能就是连蓝牙听歌,或者倒车看影像。要是这俩出问题,那体验直接归零。我在多个项目里都栽过跟头,所以今天把这部分拆开揉碎了讲。
11.1 GStreamer与Codec2框架:两条腿走路
Android的多媒体框架,从Android 10开始就全面转向Codec2了。但i.MX平台比较特殊——它既有硬件编解码器(VPU),又有GPU可以辅助处理。我个人习惯的做法是:GStreamer做管道,Codec2做底层编解码。
为什么这么搞?因为GStreamer的插件生态太丰富了。你想想看,从RTSP流媒体到本地文件播放,GStreamer都有现成的element。而Codec2是Android官方推荐的编解码接口,直接对接MediaCodec。
核心思路:GStreamer负责多媒体管道的搭建和流转,Codec2负责具体的编解码操作。两者通过一个自定义的GStreamer插件桥接。
具体来说,我们需要实现一个名为gstomx的插件,它内部调用Codec2的接口。代码结构大致如下:
// gstomxcodec2.c 核心片段
static GstFlowReturn
gst_omx_codec2_handle_frame (GstOMXCodec2 *self, GstBuffer *buffer)
{
// 将GStreamer的buffer转换为Codec2的Buffer
// 调用Codec2的queueInputBuffer()
// 等待输出,再转回GStreamer格式
...
}
嗯,这里要注意一个坑:GStreamer的buffer管理和Codec2的buffer管理机制完全不同。GStreamer用的是引用计数,Codec2用的是BufferQueue。我在项目中遇到过,如果不做内存池的适配,很容易出现内存泄漏或者死锁。
避坑指南:建议在GStreamer和Codec2之间建立一个共享内存池。我曾经因为直接拷贝数据,导致4K视频播放时CPU占用率飙到80%。后来改成ION/DMA-BUF共享内存,CPU占用直接降到15%。
11.2 视频编解码器驱动:VPU与GPU的协同
i.MX8系列芯片内置了VPU(Video Processing Unit),专门处理H.264/H.265编解码。但光有VPU还不够,很多时候需要GPU来做后处理,比如缩放、色彩空间转换。
驱动层面,我们需要关注两个东西:
- VPU驱动:通常是NXP提供的内核模块,需要配置中断、DMA、时钟等
- GPU驱动:一般是Vivante或ARM Mali,需要确保DRM/KMS框架正常
我个人的经验是,先单独验证VPU。用NXP提供的测试工具跑一下编解码循环:
# 测试H.264编码
gst-launch-1.0 videotestsrc ! vpuenc_h264 ! filesink location=test.h264
# 测试解码
gst-launch-1.0 filesrc location=test.h264 ! vpudec ! waylandsink
如果这一步能跑通,说明VPU驱动没问题。接下来才是GPU的协同工作。
注意:VPU和GPU共享同一片DDR带宽。如果同时跑4K编解码和3D渲染,带宽可能会成为瓶颈。我在一个项目中就遇到过,倒车影像和导航3D地图同时打开时,视频出现卡顿。解决办法是调整VPU的DMA优先级,或者降低GPU的渲染帧率。
11.3 音频ALSA架构适配
音频这块,Android用的是ALSA(Advanced Linux Sound Architecture)。i.MX平台通常有多个音频接口:I2S、SPDIF、HDMI Audio等。我们需要做的是:
- 确保ALSA驱动正确注册了所有声卡
- 配置
audio_policy_configuration.xml,定义音频路由 - 实现
audio_hw.c,对接ALSA的PCM接口
说白了,就是让Android知道:哪个设备是喇叭,哪个是麦克风,哪个是蓝牙。
我分享一个实际案例。有一次客户反馈,蓝牙电话没有声音。查了半天,发现是ALSA的snd_soc_dai_link配置里,蓝牙的codec没有正确绑定。代码里少了一行:
// sound/soc/fsl/imx-cs42888.c
static struct snd_soc_dai_link imx_cs42888_dai[] = {
{
.name = "BT PCM",
.stream_name = "Bluetooth PCM",
.cpu_dai_name = "fsl-sai.0",
.codec_dai_name = "bt-sco-pcm",
.platform_name = "fsl-sai.0",
.codec_name = "bt-sco",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM,
},
...
};
加上这行之后,蓝牙音频就通了。所以你看,有时候问题就这么简单,但排查起来很费时间。
11.4 Camera HAL与ISP调试
车载摄像头,说白了就是车的眼睛。i.MX平台通常有多个MIPI CSI接口,可以接2-4路摄像头。Camera HAL需要做的是:
- 初始化ISP(Image Signal Processor)
- 配置摄像头传感器(OV10640、AR0231等)
- 实现预览、拍照、录像功能
ISP调试是重头戏。你想想看,同样的摄像头,ISP参数调好了,画面清晰通透;调不好,噪点满天飞。我一般会从这几个方面入手:
| ISP模块 | 关键参数 | 调试要点 |
|---|---|---|
| 黑电平校正 | black_level | 先盖住镜头,调出纯黑画面 |
| 自动曝光 | target_luminance | 根据场景亮度调整目标值 |
| 自动白平衡 | color_gains | 用灰卡校准R/G/B增益 |
| 降噪 | noise_level | 低光照下适当提高,但别过度 |
Camera HAL的代码结构,我习惯这样组织:
camera/
├── hal/
│ ├── CameraHal.cpp # 主入口
│ ├── CameraDevice.cpp # 设备管理
│ ├── ISPManager.cpp # ISP控制
│ └── SensorDriver.cpp # 传感器驱动
├── metadata/
│ └── camera_metadata.cpp # 元数据管理
└── tests/
└── camera_test.cpp # 测试用例
调试技巧:我建议先用NXP的ISP tuning tool(比如isp_tool)在PC上调好参数,生成一个.xml配置文件。然后让Camera HAL在初始化时加载这个文件。这样就不用每次改参数都重新编译了。
11.5 知识体系总览
为了让你更直观地理解整个多媒体框架的层次关系,我画了一张图:
从这张图可以看得很清楚:我们这一章主要关注的是中间件层和驱动层。上面两层(应用层和框架层)Android已经帮我们做好了,下面两层(硬件层)是芯片原厂提供的。我们要做的,就是让中间件和驱动层完美配合。
好了,多媒体框架移植的核心内容就这些。记住一个原则:先打通驱动,再调通框架,最后优化性能。别一上来就想调ISP参数,先把视频流跑通再说。