1. 项目背景与核心需求
去年在开发一个智能家居控制系统时,我遇到了一个典型需求:用户希望将手机上的监控视频实时投射到客厅的电视大屏上。市面上的商业投屏方案要么需要额外硬件(如Chromecast),要么存在明显的延迟和画质损失。这促使我开始研究如何构建一个轻量级的局域网投屏解决方案。
MVP(Minimum Viable Product)理念在这个场景下特别适用。我们需要实现的核心功能其实很明确:
- 局域网内设备自动发现
- 视频流低延迟传输(控制在200ms以内)
- 支持至少720P分辨率
- 跨平台兼容(至少覆盖Android和Web)
经过技术选型,WebRTC成为最佳选择。它原生支持点对点传输,延迟可控制在100-300ms,且不需要中转服务器。更重要的是,其开源实现(如libwebrtc)可以让我们完全掌控传输流程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体架构分层
整个系统采用分层设计,自下而上分为:
-
设备发现层:
- 使用mDNS协议(Android端通过NSDManager实现)
- 广播包含设备类型(sender/receiver)、支持编解码器等元数据
- 发现响应时间控制在3秒内
-
信令层:
- 采用WebSocket建立初始连接
- 信令服务器用Node.js实现,代码不到200行
- 关键交换信息包括:
json复制{ "sdp": "...", "candidate": { "sdpMLineIndex": 0, "sdpMid": "0", "candidate": "candidate:..." } }
-
媒体传输层:
- 视频采集:Android端使用Camera2 API
- 编码:H.264 Baseline Profile(兼容性最好)
- 传输:基于libdatachannel的WebRTC实现
2.2 关键性能优化点
在实际测试中,我们发现三个主要性能瓶颈:
-
编码延迟:
- 默认配置下编码延迟可达80-120ms
- 通过设置以下参数优化到30ms内:
java复制mediaFormat.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0); // 实时优先级 mediaFormat.setInteger("vendor.qti-ext-enc-low-latency.enable", 1);
-
网络抖动:
- 使用TWCC(Transport Wide Congestion Control)反馈机制
- 动态调整码率(测试数据):
网络状况 初始码率 最低码率 最高码率 WiFi 5 2Mbps 1Mbps 4Mbps WiFi 6 4Mbps 2Mbps 8Mbps
-
渲染延迟:
- 接收端使用SurfaceView替代TextureView
- 开启硬件加速:
xml复制<application android:hardwareAccelerated="true">
3. Android端实现细节
3.1 视频采集模块
我们放弃了过时的Camera API,采用Camera2实现。核心流程包括:
-
创建CameraCaptureSession时指定Surface:
java复制private fun createCaptureSession() { val surfaces = mutableListOf<Surface>() surfaces.add(encoderSurface) // 编码器输入 surfaces.add(previewSurface) // 本地预览 cameraDevice.createCaptureSession(surfaces, object: CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) { this@Camera2Capturer.session = session startStreaming() } }, null) } -
设置30fps的捕获请求:
java复制captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(30, 30))
3.2 WebRTC适配层
由于原生WebRTC体积庞大(约50MB),我们做了以下精简:
-
只编译必要模块:
code复制gn gen out/Default --args='rtc_use_h264=true rtc_include_tests=false' -
自定义PeerConnectionFactory:
cpp复制PeerConnectionFactoryDependencies deps; deps.network_thread = network_thread; deps.worker_thread = worker_thread; deps.signaling_thread = signaling_thread; deps.task_queue_factory = CreateDefaultTaskQueueFactory();
4. 接收端实现方案
接收端我们提供了三种实现选择:
-
Web浏览器版:
- 使用纯JavaScript WebRTC API
- 关键代码:
javascript复制peerConnection.ontrack = (event) => { if (event.track.kind === 'video') { videoElement.srcObject = event.streams[0]; } }
-
Android TV版:
- 基于ExoPlayer的定制实现
- 支持H.265硬解(需要单独检测设备能力)
-
Linux桌面版:
- 使用GStreamer管道:
code复制webrtcbin name=recv bundle-policy=max-bundle stun-server=stun://stun.l.google.com:19302 ! queue ! h264parse ! avdec_h264 ! videoconvert ! autovideosink
- 使用GStreamer管道:
5. 实测性能数据
在不同网络环境下测试的结果如下:
| 测试场景 | 延迟(ms) | CPU占用(%) | 内存占用(MB) |
|---|---|---|---|
| 同路由器5GHz WiFi | 112±18 | 23 | 58 |
| 跨楼层2.4GHz WiFi | 187±42 | 31 | 62 |
| 有线千兆局域网 | 89±11 | 19 | 55 |
遇到的典型问题及解决方案:
-
绿屏问题:
- 现象:接收端偶尔出现绿色画面
- 原因:SPS/PPS帧丢失
- 修复:在onSdpSuccess回调中强制插入参数集:
javascript复制let newSdp = sdp.replace( /a=fmtp:(\d+) (.+)\r\n/g, `a=fmtp:$1 $2; sprop-parameter-sets=${base64Params}\r\n` );
-
音频不同步:
- 调整RTCP SR/RR报告间隔为500ms
- 在Android端添加音频时间戳校准:
java复制audioTrack.setPlaybackHeadPosition( SystemClock.elapsedRealtimeNanos() / 1000 * sampleRate / 1000000 );
这个项目已经开源在GitHub(为避免链接问题暂不展示具体地址),核心代码约3500行,编译后的APK大小控制在8MB以内。对于想深入研究的开发者,建议从信令交换和码率自适应这两个模块开始阅读,它们是保证流畅体验的关键。
