一、导航投屏应用:手机导航投射到车机
各位同学,今天我们来聊聊车机互联里最实用的场景——导航投屏。
说实话,我做了这么多年车机开发,发现用户最刚需的功能就是导航。手机上的导航App已经非常成熟了,高德、百度、腾讯,哪个不是功能齐全?但车机上的导航嘛……嗯,你懂的,更新慢、数据老、交互还别扭。
所以,把手机导航投射到车机屏幕上,就成了一个绕不开的需求。这一章,我们就手把手把这个功能做出来。
1.1 整体架构:手机端 + 车机端
先看整体设计。导航投屏的核心逻辑其实不复杂:
- 手机端:运行导航App,采集导航画面和语音指令
- 车机端:接收画面流,显示在车机屏幕上,同时支持语音交互
- 通信通道:通过WiFi或USB,传输视频流和指令数据
我个人习惯把这种架构叫做「双端镜像+指令回传」模式。说白了,手机是大脑,车机是显示器加麦克风。
核心要点:导航投屏不是简单的屏幕镜像,而是要有交互能力。用户在车机上说话,手机端要能听懂并响应。
1.2 手机端:画面采集与编码
手机端要做两件事:采集导航画面和编码传输。
画面采集,我推荐用 MediaProjection API。这是Android官方提供的屏幕录制方案,不需要root权限。我在项目里踩过一个坑——MediaProjection 需要用户授权,而且授权弹窗只能弹一次。如果用户拒绝了,你得引导他去设置里手动开启。
小技巧:采集分辨率不要用手机原生分辨率,建议降到720p。画面清晰度足够,带宽占用能降一半。
编码方面,用 MediaCodec 做硬件编码,输出H.264流。码率控制在2-4Mbps,帧率15fps就够了。导航画面变化不频繁,没必要跑满30帧。
// 手机端:初始化MediaProjection和编码器
MediaProjectionManager projectionManager =
(MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
Intent intent = projectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE);
// 编码器配置
MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1280, 720);
format.setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
MediaCodec encoder = MediaCodec.createEncoderByType("video/avc");
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1.3 车机端:画面接收与渲染
车机端相对简单,主要工作是接收视频流并解码显示。
我用的是 SurfaceView 配合 MediaCodec 解码。为什么不用 TextureView?嗯,这里有个经验——SurfaceView 在车机上的性能更稳定,特别是低端芯片上,TextureView 容易掉帧。
解码后的画面直接渲染到 SurfaceView 上,用户看到的就是手机导航的实时画面。
// 车机端:解码并显示
MediaCodec decoder = MediaCodec.createDecoderByType("video/avc");
decoder.configure(format, surface, null, 0);
decoder.start();
// 从网络读取数据,送入解码器
byte[] h264Data = readFromSocket();
int inputIndex = decoder.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
inputBuffer.put(h264Data);
decoder.queueInputBuffer(inputIndex, 0, h264Data.length, 0, 0);
}
1.4 语音交互:车机说话,手机听懂
这是导航投屏的亮点功能。用户在车机上说出「导航到天安门」,车机把语音数据传给手机,手机识别后执行导航操作。
具体流程是这样的:
- 车机通过麦克风采集用户语音,保存为PCM音频数据
- 车机将音频数据通过WebSocket发送给手机
- 手机端调用系统语音识别(或第三方SDK)进行识别
- 手机端解析识别结果,执行导航操作
- 手机端将执行结果(如「已规划路线」)回传给车机显示
注意:语音数据量不小。16kHz采样、16bit、单声道,一秒就是32KB。如果网络不好,建议先做压缩再传输。我曾经遇到过WiFi信号差,语音数据延迟3秒才到手机,用户体验极差。
语音识别这块,我建议用手机自带的 SpeechRecognizer。它不需要额外接入第三方SDK,而且识别准确率在导航场景下够用了。如果你需要更专业的导航语义理解,可以接入高德或百度的语音SDK。
// 车机端:采集语音并发送
AudioRecord recorder = new AudioRecord(
MediaRecorder.AudioSource.MIC, 16000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, 4096);
recorder.startRecording();
byte[] buffer = new byte[4096];
while (isRecording) {
int read = recorder.read(buffer, 0, buffer.length);
webSocket.send(buffer, 0, read); // 实时发送
}
// 手机端:接收并识别
SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(context);
recognizer.setRecognitionListener(new RecognitionListener() {
@Override
public void onResults(Bundle results) {
ArrayList<String> matches =
results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
String command = matches.get(0);
// 解析指令,执行导航
handleNavigationCommand(command);
}
});
1.5 通信协议:选TCP还是WebSocket?
这个问题我经常被问到。我的建议是:视频流用TCP,指令用WebSocket。
| 数据类型 | 推荐协议 | 原因 |
|---|---|---|
| 视频流(H.264) | TCP Socket | 需要可靠传输,丢帧会导致花屏 |
| 语音指令 | WebSocket | 双向通信,延迟低,易于解析 |
| 控制指令(暂停/恢复) | WebSocket | 轻量级,实时性要求高 |
为什么视频不用WebSocket?因为WebSocket的帧开销比较大,视频流数据量大,用TCP裸流更高效。我在项目里测试过,同样码率下,TCP的延迟比WebSocket低20-30ms。
1.6 避坑指南:我曾经踩过的三个坑
做这个项目时,我遇到了不少问题。挑三个典型的跟大家说说:
- 坑一:画面黑屏——
MediaProjection在部分手机上需要FOREGROUND_SERVICE权限。不加的话,服务一进后台就被杀了,画面直接黑掉。 - 坑二:语音延迟大——车机采集的语音数据如果先存成文件再发送,延迟会很高。改成流式发送,边采集边传,延迟降到200ms以内。
- 坑三:车机解码卡顿——有些车机芯片解码能力弱,720p的H.264都跑不动。后来我加了自适应码率,根据车机性能动态调整分辨率和码率。
总结一下:导航投屏的核心就三块——画面采集传输、语音交互、通信协议。把这三点打通,一个可用的导航投屏应用就出来了。下一章我们会在这个基础上,加入更多交互功能,比如车机端触控操作导航。
公众号:蓝海资料掘金营,微信deep3321