一、导航投屏应用:手机导航投射到车机

各位同学,今天我们来聊聊车机互联里最实用的场景——导航投屏。

说实话,我做了这么多年车机开发,发现用户最刚需的功能就是导航。手机上的导航App已经非常成熟了,高德、百度、腾讯,哪个不是功能齐全?但车机上的导航嘛……嗯,你懂的,更新慢、数据老、交互还别扭。

所以,把手机导航投射到车机屏幕上,就成了一个绕不开的需求。这一章,我们就手把手把这个功能做出来。

1.1 整体架构:手机端 + 车机端

先看整体设计。导航投屏的核心逻辑其实不复杂:

  • 手机端:运行导航App,采集导航画面和语音指令
  • 车机端:接收画面流,显示在车机屏幕上,同时支持语音交互
  • 通信通道:通过WiFi或USB,传输视频流和指令数据

我个人习惯把这种架构叫做「双端镜像+指令回传」模式。说白了,手机是大脑,车机是显示器加麦克风。

核心要点:导航投屏不是简单的屏幕镜像,而是要有交互能力。用户在车机上说话,手机端要能听懂并响应。

手机端 导航App(高德/百度) 画面采集模块 语音识别引擎 通信客户端 车机端 画面接收与渲染 语音采集模块 指令转发模块 通信服务端 视频流(H.264) 语音指令(JSON) TCP / WebSocket

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 语音交互:车机说话,手机听懂

这是导航投屏的亮点功能。用户在车机上说出「导航到天安门」,车机把语音数据传给手机,手机识别后执行导航操作。

具体流程是这样的:

  1. 车机通过麦克风采集用户语音,保存为PCM音频数据
  2. 车机将音频数据通过WebSocket发送给手机
  3. 手机端调用系统语音识别(或第三方SDK)进行识别
  4. 手机端解析识别结果,执行导航操作
  5. 手机端将执行结果(如「已规划路线」)回传给车机显示

注意:语音数据量不小。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