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等。我们需要做的是:

  1. 确保ALSA驱动正确注册了所有声卡
  2. 配置audio_policy_configuration.xml,定义音频路由
  3. 实现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 知识体系总览

为了让你更直观地理解整个多媒体框架的层次关系,我画了一张图:

i.MX Android Automotive 多媒体框架层次 应用层:MediaPlayer / Camera2 / AudioTrack 框架层:MediaCodec / Camera HAL / AudioFlinger 中间件层:GStreamer / Codec2 / ALSA lib 驱动层:VPU驱动 / GPU驱动 / ALSA驱动 / ISP驱动 硬件层:i.MX SoC / VPU / GPU / Camera Sensor / Audio Codec 本章重点

从这张图可以看得很清楚:我们这一章主要关注的是中间件层和驱动层。上面两层(应用层和框架层)Android已经帮我们做好了,下面两层(硬件层)是芯片原厂提供的。我们要做的,就是让中间件和驱动层完美配合。

好了,多媒体框架移植的核心内容就这些。记住一个原则:先打通驱动,再调通框架,最后优化性能。别一上来就想调ISP参数,先把视频流跑通再说。

公众号:蓝海资料掘金营,微信deep3321