27、USB车载系统开发(Android Auto):Android Auto USB连接协议、AAudio与车载音频路由、车载USB Hub管理、实战:开发车载USB音乐播放器
车载USB开发,说实话,是Android系统开发里比较“硬核”的一块。你不仅要懂协议,还得懂音频路由,甚至要处理物理Hub的供电问题。我当年第一次做车载项目时,就被USB枚举失败折腾了两天——后来发现是线缆阻抗不匹配。嗯,今天我们就来把这些坑一个个填平。
Android Auto USB连接协议
Android Auto的核心,说白了就是手机把屏幕“投射”到车机上。但车机怎么知道插上来的是个Android手机,而不是U盘?这就要靠USB连接协议来握手了。
整个连接过程分三步:
- 设备枚举:车机作为USB Host,检测到设备插入后,读取手机的设备描述符。
- 协议协商:手机端会声明自己支持Android Auto协议(通过特定的VID/PID或接口描述符)。
- 角色切换:一旦确认支持,车机就会启动AAudio会话,接管音频流。
关键点:Android Auto要求车机必须支持USB 2.0及以上,且必须实现AOA(Android Open Accessory)协议。没有AOA,手机根本不会进入车载模式。
我在项目中遇到过一个问题:某款车机枚举手机时总是超时。后来发现是车机的USB控制器驱动里,对高速设备的握手时序要求太严格。解决办法是给车机固件打了一个补丁,放宽了SOF(Start of Frame)包的容忍度。
AAudio与车载音频路由
音频路由是车载开发里最容易出幺蛾子的地方。手机播放音乐,声音要从手机扬声器出来,还是从车载音响出来?答案是后者。但怎么把音频数据从手机“搬”到车机?
Android Auto使用AAudio作为音频管道。AAudio是Android 8.0引入的原生音频API,延迟低、性能好。在车载场景下,它的工作流程是这样的:
- 手机端AAudio捕获应用输出的PCM数据。
- 数据通过USB Audio Class(UAC)协议传输到车机。
- 车机端的音频DSP解码后,输出到车载功放。
避坑指南:我曾经遇到一个案例,手机播放音乐时车机有声音,但导航语音却从手机扬声器出来。原因在于导航应用使用了不同的音频流类型(STREAM_VOICE_CALL),而车机只路由了STREAM_MUSIC。解决方案是在车载端实现多流路由策略,把所有非通话音频都重定向到USB Audio。
AAudio的采样率设置也很关键。车载音响通常支持48kHz,但有些老车机只支持44.1kHz。如果采样率不匹配,音频就会变调或出现爆音。我建议在车机端实现一个采样率转换器(SRC),自动适配手机输出的格式。
车载USB Hub管理
现在的车机通常不止一个USB口。中控一个,扶手箱一个,后排可能还有两个。这些口背后往往挂着一个USB Hub。Hub管理不好,就会出现“插了U盘识别不了”、“手机充电断断续续”等问题。
车载USB Hub管理的核心要点:
- 电源管理:每个端口必须有过流保护。我曾经见过一个设计,Hub总电流只有1.5A,但接了手机、U盘和行车记录仪,结果电压被拉低到4.2V,所有设备都掉线了。
- 端口映射:车机系统需要知道哪个物理端口对应哪个功能。比如端口1专用于Android Auto,端口2用于U盘。这需要在内核的USB gadget驱动里做端口策略。
- 热插拔处理:车载环境振动大,USB接口容易松动。系统必须能快速检测到断开事件,并恢复音频路由。
注意:不要把所有USB端口都配置成Host模式。至少保留一个端口支持OTG,用于调试和固件升级。否则一旦系统崩溃,你连adb都连不上。
我个人的习惯是,在车机的init.rc里加一个usb管理服务,专门监控Hub的状态变化。一旦检测到端口过流,立即切断该端口电源并上报日志。这样即使硬件保护没触发,软件也能兜底。
实战:开发车载USB音乐播放器
理论说完了,我们来写点代码。下面是一个简化版的车载USB音乐播放器核心逻辑,它负责从U盘读取音乐文件,并通过AAudio路由到车载音响。
// 伪代码:车载音乐播放器核心模块
class CarMusicPlayer {
private AudioTrack audioTrack;
private UsbDevice usbDevice;
private File currentFile;
public void playFromUsb(UsbDevice device) {
// 1. 挂载U盘
UsbManager usbManager = getSystemService(USB_SERVICE);
UsbInterface usbInterface = device.getInterface(0);
UsbEndpoint endpoint = usbInterface.getEndpoint(0);
// 2. 读取文件(假设是FAT32格式)
File musicDir = new File("/mnt/usb_storage/Music");
File[] files = musicDir.listFiles((dir, name) -> name.endsWith(".mp3"));
// 3. 初始化AAudio
AudioAttributes attrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
AudioFormat format = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(48000)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build();
audioTrack = new AudioTrack(attrs, format, 4096, AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
// 4. 解码并播放
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(currentFile.getAbsolutePath());
MediaFormat trackFormat = extractor.getTrackFormat(0);
extractor.selectTrack(0);
byte[] buffer = new byte[4096];
while (extractor.readSampleData(buffer, 0) > 0) {
audioTrack.write(buffer, 0, buffer.length);
extractor.advance();
}
audioTrack.play();
}
}
这段代码虽然能跑,但实际项目里要复杂得多。比如:
- U盘拔出时要暂停播放并释放资源。
- 要支持多种音频格式(FLAC、APE、WMA)。
- 要处理ID3标签,显示歌名和专辑封面。
我记得有一次,用户反馈说播放器在切换歌曲时会“啪”一声爆音。查了半天,发现是AudioTrack在stop()和play()之间没有做静音处理。解决方案是在切换间隙插入一段静音PCM数据。
知识体系总览
下面这张图总结了本章的核心知识点和它们之间的关系。你可以把它当作车载USB开发的“地图”。
从这张图可以看出,车载USB开发不是孤立的。协议层负责“连得上”,音频层负责“听得清”,Hub层负责“稳得住”。三者缺一不可。实战中的音乐播放器,就是把这些模块串起来的最终产物。
个人经验:如果你刚开始做车载开发,我建议先从USB枚举和音频路由入手。这两个模块最容易出问题,也最能积累经验。Hub管理虽然重要,但更多是硬件和内核层面的工作,软件工程师可以先放一放。
好了,关于车载USB开发的核心内容就这些。记住,车载环境比手机复杂得多——温度、振动、电源波动都会影响USB通信。多在实际车上测试,别只在开发板上跑。嗯,今天就到这里。
公众号:蓝海资料掘金营,微信deep3321