简介:EasyScreenLive推流组件是一款集采集、编码、组播、推流和RTSP服务于一体的同屏工具,主要面向需要快速实现大屏投屏、无纸化会议同屏及课堂同屏演示的软件开发者和项目集成人员。资源包共408个文件,总大小约23.64MB,核心部分包含主程序、动态库、静态库与头文件,可用于二次开发和集成调用;其中336个PNG图片素材覆盖按钮、图标、滑块等常见界面元素,另有JPG、BMP界面资源以及XML配置文件,方便定制界面和调整推流参数。截至目前已有518人学习浏览,同屏推流场景下的资料相对稀缺,该组件结构较为完整,适合中高级开发者作为参考实现。方案内同时提供了皮肤图片、图标、动画等资源,便于快速搭建演示环境、验证RTSP推流效果,或直接嵌入现有项目,减少自研推流服务的前期工作量。
1. 同屏推流没你想的那么简单:先把 EasyScreenLive 的边界摸清楚
做同屏推流最尴尬的时刻,不是服务端挂掉,而是你把 EasyScreenLive 这类推流组件跑起来之后,所有参数看起来都对,播放器却一直黑屏。这个场景我经历过不止一次,后来才发现,问题往往不出在推流逻辑,而出在你把它塞进自己业务时的选型和播放端习惯。EasyScreenLive 这类组件做的事情,是把屏幕采集、音频编码、协议打包、数据发送串成一条闭环,让你不用从 RTP 封包开始攒。适合两类人:想尽快把同屏能力集成进 App 的 Android 开发者,以及做可视化大屏、设备巡检、远程协助的后端兼端侧工程师。这篇就从协议选型、链路搭建、参数调优到常见故障排查,把这条推流链路的细节拆开讲。
2. RTSP/RTMP 怎么选:协议决定延迟上限和落地方案
2.1 延迟模型:RTSP 走 UDP 传流,RTMP 被 TCP 重传拖住
同屏场景的延迟敏感度远高于直播。直播端到端 2 到 5 秒可接受,同屏如果延迟超过 500 毫秒,配合者在屏幕上画圈时位置就明显偏差,远程操作也会有“慢半拍”的感觉。所以协议选型不是拍脑袋,而是先想清楚你的传输路径和容错机制。
RTSP 全称 Real Time Streaming Protocol,它本身只负责会话管理,OPTIONS、DESCRIBE、SETUP、PLAY 四条命令走完就建立会话,真正的媒体数据靠 RTP 传输,底层可以选 UDP 或 TCP。同屏场景默认选 UDP,原因很简单:UDP 不重传、不维护拥塞窗口,数据到达速度只受限于编码输出和网络带宽,延迟模型更接近“发出即走”。播放器端只要把缓冲队列压浅,端到端延迟就能控制在 300 毫秒以内。
RTMP 则是把 H.264/AAC 封装进 FLV 标签,整个跑在 TCP 长连接上。TCP 的可靠传输是双刃剑:弱网下丢一个包,TCP 要重传,而重传期间后续数据会被阻塞在发送窗口内;播放器端要等数据补齐再渲染,表现是画面停顿、延迟一点点被拉长。所以同样的编码参数,RTMP 在 5% 丢包下的延迟表现往往比 RTSP over UDP 差一大截。这不是说 RTMP 不好,而是它的设计目标本来就是“稳定不分发”,并不是“低延迟优先”。
这里有个容易混淆的点:RTSP 也可以跑在 TCP 上,也就是 RTP over TCP 模式。这个模式的好处是穿防火墙容易,不需要额外映射 UDP 端口,但 TCP 重传问题同样存在,弱网下仍然会被阻塞拖住。对局域网同屏来说,UDP 是首选;跨公网或 NAT 环境,走 TCP 的 RTSP 比 UDP 更不容易被运营商掐流。我一般这样定:局域网内同屏走 RTSP/UDP,跨公网尽量走 RTSP/TCP,如果网络条件不稳定就直接切 RTMP 依赖 TCP 的可靠传输。
2.2 同屏为什么默认走 RTSP:场景决定优先级
同屏场景的核心诉求就三条:延迟低、画面实时、随时可重连。RTSP 在这三点的表现都比 RTMP 更贴合。
延迟方面,RTSP over UDP 天然快,不需要等 TCP 确认,播放器端缓冲可以压得很浅。重连方面,RTSP 的会话可以随时用新的 SETUP 和 PLAY 续上,播放器掉线后重走一遍命令即可恢复,不需要像 RTMP 那样重新建立完整的长连接握手。播放端生态方面,VLC、ffplay、各类内嵌播放器对 RTSP 地址的解析都很成熟,不需要额外引入 FLV 解析库,接入成本低。
RTMP 的强项是跨端分发。如果要把同屏画面推到直播平台或 CDN 做大规模分发,RTMP 有现成生态;但点对点远程协助、局域网设备同屏,RTSP 更直接。这里特别要核对的是 EasyScreenLive 这类组件的输出能力。有的组件默认只输出 RTMP,需要配置开关切到 RTSP Server 模式;如果组件只暴露 RTMP 推流接口,那播放端就无法用裸 RTSP URL 直接打开,需要在服务端做一层转拉或转推。选组件时把“我要的是 RTSP 还是 RTMP”确认清楚,优先级比编码器参数更高,否则后期推到一半换协议,改造成本非常大。
2.3 用一份配置同时支持双协议:切换骨架与运行边界
实际项目里我会在推流前做协议分支,而不是在编译期写死。先定义一份协议配置:
public class PushConfig { public static final int PROTOCOL_RTMP = 1; public static final int PROTOCOL_RTSP = 2; public int protocol = PROTOCOL_RTSP; // 同屏场景默认 RTSP public String rtmpUrl; // "rtmp://192.168.1.20:1935/live/room01" public String rtspUrl; // "rtsp://192.168.1.20:8554/live/room01" public int videoWidth = 720; public int videoHeight = 1280; public int videoFps = 15; public int videoBitrate = 2_000_000; // 2 Mbps }逻辑说明:协议被封装成配置字段,运行时根据业务场景选择推流链路。同屏到局域网设备用 RTSP,需要公网分发时切到 RTMP,避免每换一个场景就改一遍代码。这一步不涉及具体推流实现,只是把抽象边界提前划好,省掉后面联调时大量改造成本。
参数说明:rtmpUrl 和 rtspUrl 分别对应两种协议的推流地址;videoWidth 和 videoHeight 要与后面编码器、虚拟显示的分辨率保持一致,一旦改了分辨率,编码器、虚拟显示、RTP 打包三处要同步改,否则出现比例不对或编码器重新配置导致的卡顿。videoBitrate 是编码器目标码率,这里单位是 bit/s,注意别写成 KB/s。
切换时按协议实例化不同的推流器,接口保持一致:
if (config.protocol == PushConfig.PROTOCOL_RTSP) { RtspPusher pusher = new RtspPusher(); pusher.start(config.rtspUrl, encoderOutput); } else if (config.protocol == PushConfig.PROTOCOL_RTMP) { RtmpPusher pusher = new RtmpPusher(); pusher.start(config.rtmpUrl, encoderOutput); }逻辑说明:两个推流器内部逻辑完全不同,但对外暴露 start(url, dataSource) 和 stop() 两个方法,上层业务不感知具体协议。要注意的是不要在同一进程内同时跑两条推流链路,线程模型和缓冲管理会互相干扰,内存开销翻倍,先保证一条链路稳定再考虑扩展。
3. 从零拉通一条推流链路:屏幕采集、编码与 RTSP 发送
3.1 屏幕采集:MediaProjection 会话的启动顺序
Android 同屏的第一步是拿到屏幕图像,标准做法是 MediaProjection + VirtualDisplay。启动顺序有讲究:先申请权限弹窗,用户授权后拿到 MediaProjection 实例,再创建 VirtualDisplay,最后把 VirtualDisplay 的 Surface 作为 MediaCodec 的输入。这个顺序不能颠倒,否则拿不到有效的投影会话。
MediaProjectionManager mpManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); Intent captureIntent = mpManager.createScreenCaptureIntent(); startActivityForResult(captureIntent, REQUEST_CODE);onActivityResult 拿到 resultCode 和 data 后,创建投影会话:
MediaProjection projection = mpManager.getMediaProjection(resultCode, data); VirtualDisplay display = projection.createVirtualDisplay( "ScreenCapture", screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, encoderSurface, null, null);逻辑说明:resultCode 和 data 是系统授权屏幕捕获的关键凭证,缺少任何一个 createVirtualDisplay 都会抛异常。MediaProjection 是系统服务的句柄,VirtualDisplay 在这里把屏幕内容镜像到 encoderSurface 上,编码器从 Surface 上取原始帧。整条采集链路的时序是:授权弹窗 -> MediaProjection -> VirtualDisplay -> 编码器输入面,中间任何一步回调失败都要有兜底,否则推流端拿到的是全黑画面。
参数说明:screenWidth 和 screenHeight 建议通过 WindowManager 读取真实屏幕尺寸,不要写死,否则虚拟显示会做缩放缩放,画面发糊;screenDensity 用系统默认 density 即可。VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR 让虚拟屏幕自动镜像主屏幕内容,这个标志位下,系统的密码输入框、敏感页面会被系统自动隐藏,做远程协助时要提前和产品对齐这个安全行为,别让测试人员误以为画面采集坏了。
3.2 编码器配置:H.264 硬编与 Surface 输入
屏幕采集拿到原始画面之后直接送 MediaCodec 硬编。这里要特别注意用 Surface 输入而不是 ByteBuffer 输入。Surface 输入的本质是 GPU 与编码器之间的零拷贝通道,省掉一次 YUV 数据从内存到编码器的搬运,对降低延迟非常关键。ByteBuffer 输入还要手动管理帧缓冲和 YUV 转换,踩坑概率大得多。
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000); // 2 Mbps format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // 15 fps format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); // 2 秒一个关键帧 format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); MediaCodec encoder = MediaCodec.createEncoderByType("video/avc"); encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); encodder.setCallback(new MediaCodec.Callback() { @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { // 取编码后的 H.264 数据,交给 RTP 打包模块 } });逻辑说明:KEY_COLOR_FORMAT 必须设置成 COLOR_FormatSurface,否则 encoder.configure 会直接抛异常,这一点很多踩坑帖都提到过。I_FRAME_INTERVAL 决定关键帧间隔,间隔越短,播放器起播越快,但码率消耗更高。这里选 CBR 模式是为了保证弱网下码率不突变,延迟更容易控制。
参数说明:2 Mbps 在 720p、15fps 的静态画面下足够清晰,如果画面大幅动起来出现马赛克,把码率提到 3 到 4 Mbps。帧率设 15 能明显降低编码压力和发热,不必追求 60fps 录屏,同屏场景 15 到 20 帧足够用户看清操作。BITRATE_MODE_CBR 在弱网下表现更稳,画面变化大的场景偶尔出现模糊度波动,但延迟优先,这点小事可以接受。
3.3 RTSP 握手:OPTIONS、DESCRIBE、SETUP、PLAY
RTSP 的会话流程是固定的四步,任何一步返回错误码都会导致推流失败。最常用的推流流向是组件作为客户端,把编码数据推给 RTSP Server,播放器再从 Server 拉流。下面是一个简化实现:
Socket socket = new Socket(host, port); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())); BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); // 1. OPTIONS 探测服务器能力 writer.write("OPTIONS rtsp://" + host + ":" + port + "/live/room01 RTSP/1.0\r\n"); writer.write("CSeq: 1\r\n\r\n"); writer.flush(); // 2. DESCRIBE 获取 SDP 描述 writer.write("DESCRIBE rtsp://" + host + ":" + port + "/live/room01 RTSP/1.0\r\n"); writer.write("CSeq: 2\r\n"); writer.write("Accept: application/sdp\r\n\r\n"); writer.flush(); // 3. SETUP 建立传输通道 writer.write("SETUP rtsp://" + host + ":" + port + "/live/room01/track1 RTSP/1.0\r\n"); writer.write("CSeq: 3\r\n"); writer.write("Transport: RTP/AVP/UDP;unicast;client_port=5004-5005\r\n\r\n"); writer.flush(); // 4. PLAY 启动推流 writer.write("PLAY rtsp://" + host + ":" + port + "/live/room01 RTSP/1.0\r\n"); writer.write("CSeq: 4\r\n\r\n"); writer.flush();逻辑说明:SETUP 告诉服务器客户端用哪个端口收 RTP 数据,响应里返回的 server_port 是数据发送的真正目标端口。后面 RTP 数据就要发往这个 server_port,不是推流 URL 里的端口。CSeq 每个请求必须递增,VLC 等播放器对 CSeq 比较敏感,序号乱了服务器直接回 400。
参数说明:track1 是服务器在 SDP 描述里定义的流标识,写错了 SETUP 会失败。client_port 是两个连续的 UDP 端口,一个收 RTP、一个收 RTCP,如果端口被别的进程占用,要换一组再试。RTSP 鉴权如果服务器开了 Basic 认证,每个请求都要带 Authorization 头,这个放在第 5 章展开讲。
3.4 把编码输出和 RTSP 发送串起来
编码器输出的数据通过 onOutputBufferAvailable 拿到,关键帧和非关键帧要区别对待。解码器必须从关键帧开始才能出画面,所以 SETUP 之后如果先发了非关键帧,播放器就长时间黑屏。一般我在输出循环里维护一个状态机:等第一个 KEY_FRAME 出现才启动 RTP 发送。
public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) { ByteBuffer buffer = codec.getOutputBuffer(index); boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0; if (isKeyFrame) { rtpSender.sendSpsPps(); // 先发 SPS/PPS rtpSender.sendPacket(buffer, info); // 再发关键帧数据 } else if (rtpSender.started()) { rtpSender.sendPacket(buffer, info); // 只有会话建立后才发非关键帧 } codec.releaseOutputBuffer(index, false); }逻辑说明:SPS/PPS 是解码器初始化必需的参数集,必须随关键帧一起提前发,否则新加入的播放器无法解析码流。rtpSender.started() 判断会话是否经过 SETUP,这个状态位要在 RTSP 握手成功后置真。
RTP 打包时注意 12 字节头部的字节序:版本号、序列号、时间戳、SSRC 全部按大端序写入。时间戳按 90kHz 换算,视频帧时间戳直接用编码器输出的 presentationTimeUs 乘 90 再除以 1000,序列号从随机值开始避免和上一会话冲突。这些细节决定了播放器能不能稳定解析,别在这上面省事。
4. 延迟与稳定性调优:六个关键参数与实测效果
4.1 码率控制:弱网优先 CBR,画质优先 VBR
码率控制是推流稳定性的第一道闸。CBR 模式下码率恒定,带宽占用可预测,弱网下抗抖动能力更强;VBR 在静态画面下码率很低,画面大幅变化时码率突增,如果网络带宽不够,编码器输出积压,延迟随之拉高。
我的经验是同屏场景无脑选 CBR。可视化大屏这类静态画面多、但网络质量一般的环境,CBR 2 Mbps 效果稳定;如果要保证远程操作过程中的画面清晰度,可以把 CBR 目标码率提到 4 Mbps。VBR 只适合局域网内高品质投屏,网络带宽充足时画质更好,但遇到弱网会有明显卡顿。编码器参数一句话总结:图稳定用 CBR,图画质用 VBR,别两边摇摆。
4.2 GOP 与 I 帧间隔:关键帧越密,起播越快
I 帧间隔直接决定播放器从打开到看到画面的等待时间。间隔 2 秒,播放器平均最多等 2 秒出画面;间隔 4 秒,最坏要等 4 秒。另外弱网丢包会导致 I 帧之前的 P 帧全部失效,I 帧越密,画面恢复越快。
代价是码率上升,I 帧本身比 P 帧大 10 到 20 倍。通常 2 秒间隔是性价比最高的平衡点,不要追求 1 秒,否则静态画面下码率浪费很多。远程协助场景还有人会把间隔调到 4 秒来省带宽,前提是网络稳定,否则播放器容易在长时间静止后突然跳变。
4.3 分辨率与帧率:不是越高越好
同屏的目标是看清操作,不是影视级画质。1080p 60fps 编码负载和码率双高,发热严重反而掉帧,我一般建议 720p@15fps 作为默认。操作细节看不清再升到 20fps 或 1080p,不要一上来就拉满。
编码器在 720p 下可以轻松跑满 30fps,但 15fps 与 30fps 对用户感知来说差别不大——屏幕内容大部分时间是静态的,15fps 足够。降帧率是降低发热和耗电最有效的手段,比降分辨率直观得多。
4.4 音频参数与音画同步:时间戳按采样率换算
音频同步问题多数出在时间戳上。视频时间戳按 90kHz 走,音频如果直接用帧计数当毫秒发送,两边必然对不上。音频时间戳换算规则是这样的:采样率 44100 Hz,每帧 1024 个采样,那么每一帧音频的时间戳增量就是 1024 * 1000000 / 44100 微秒。
同屏场景对音频质量要求不高,采样率用 44100、单声道就够了,码率 96 kbps 以下即可。关键是音频和视频各自线程推进时间戳时,要定期对齐一次基准时间,差值超过 40 毫秒就丢弃迟到的音频帧,保证口型一致。这个问题第 5 章还会展开讲。
4.5 播放端缓冲与丢包恢复:两个容易被忽略的端
播放器端的缓冲设置比推流端更影响观感。VLC 打开 RTSP 拉流默认有缓冲,可以通过命令行参数调浅,缓冲区压到 300 毫秒以内再看实时效果。有的播放器把缓冲固定到 1 秒以上,推流端延迟再低也白搭。
推流端走 UDP 丢包后,最简单的恢复策略是等播放器回传 PLI(Picture Loss Indication),收到后编码器立即强制输出一个 IDR 帧。很多组件的“画面花屏几秒不动”问题就是没处理 PLI,或者处理了但间隔太长。这一项要在联调时专门验证,做一次丢包测试比调半天参数都管用。
下表是六个参数的推荐起点,按同屏场景默认值整理:
| 参数 | 推荐值 | 适用场景 | 备注 |
|---|---|---|---|
| 码率模式 | CBR | 弱网或固定带宽 | 画质优先改 VBR |
| 目标码率 | 2 Mbps | 720p@15fps | 画面复杂提到 4 Mbps |
| I 帧间隔 | 2 秒 | 默认通用 | 4 秒节省带宽 |
| 分辨率 | 720p | 默认 | 看不清细节再升 1080p |
| 帧率 | 15 fps | 默认 | 操作密集场景 20 fps |
| 音频采样率 | 44100 Hz / mono | 默认 | 96 kbps 以下码率 |
5. 常见问题排查:黑屏、鉴权失败、高延迟的五类翻车现场
5.1 黑屏 / 无画面
现象:推流日志显示编码器在输出,Socket 发送也没报错,但播放器打开后长时间黑屏,或者花屏几秒后黑屏。
原因:多数是首个 I 帧没有发到播放器。一种情况是 SETUP 之后立刻发送编码数据,而编码器输出的第一帧是 P 帧;另一种是 SPS/PPS 没有随关键帧一起发送,解码器初始化失败。RTP 时间戳和序列号不从 0 或随机值开始,也会导致播放器解析异常,表现同样是黑屏。
解决:先确保 SETUP 做成功后,等 MediaCodec 输出 BUFFER_FLAG_KEY_FRAME 再启动发送,发送前先打一包 SPS/PPS。RTP 时间戳按 90kHz 换算,序列号初始化成随机 16 位值,避免跨会话冲突。做到这三步,黑屏问题基本消除。
5.2 推流地址鉴权失败
现象:RTSP 握手在 DESCRIBE 阶段返回 401 Unauthorized,或者 SETUP 阶段返回 401,但前面的 OPTIONS 都正常。
原因:RTSP 的 Basic 认证不是一次性的。有些组件只在 DESCRIBE 请求上加了 Authorization 头,SETUP 和 PLAY 阶段漏带,服务器在任何一个阶段都可以要求重新认证。另一个坑是 Base64 编码格式写错,RFC 7617 要求的是 "username:password" 整体编码,少了冒号或者空格都会失败。
解决:把用户名密码按 "username:password" 格式 Base64 编码,然后在 OPTIONS、DESCRIBE、SETUP、PLAY 四个请求头中都带上 Authorization: Basic xxx。这样无论服务器哪个阶段校验都能通过。校验一下编码结果:用户名密码为 admin/admin888 时,Base64 串应该以 YWRtaW46 开头。
5.3 播放器反复重连
现象:播放器能出画面,稳定几秒到几十秒后画面冻结,紧接着播放器显示重新连接,重连后又恢复,如此循环。
原因:可能是 RTP 序列号在同一会话内发生回绕或跳变,播放器判定流状态异常主动断开;也可能是时间戳单位不正确,播放器计算帧间隔异常。还有一种情况是 RTSP 服务器开了 session 超时,而推流端没有按规则定期发送 GET_PARAMETER 保活请求,服务端回收了会话。
解决:先抓包看 RTP 头的序列号和时间戳,确认没有跳变。随后在 SETUP 响应里取 session 字段,在推流过程中每隔 15 到 30 秒发一次 GET_PARAMETER 请求,每次都要携带正确的 session 标识。这个保活机制很多人忽略,服务器端的 session 超时从 30 秒到 2 分钟不等,不加保活就会周期性断流。
5.4 音画不同步
现象:画面和声音误差逐渐增大,重启播放器后恢复同步,过一段时间又开始偏。误差在连续推送 5 分钟后达到几百毫秒。
原因:音频和视频在两个线程推流,时间戳各自为政。最典型的是音频时间戳用采样帧计数直接当毫秒发,视频时间戳用 90kHz,两个基准没有统一换算。另一个原因是无音频数据时组件没有插入静音帧,音频时间戳停滞,视频时间戳继续推进,播放器对齐逻辑出错。
解决:把音视频时间戳统一到同一条时钟轴,视频按 90kHz、音频按采样率换算成微秒,对比时同时转成毫秒。定期比较两端时间戳,差值超过 40 毫秒就丢弃迟到的音频帧或者视频帧。如果组件支持静音帧插入,把它打开,保证音频时间戳持续递增。
5.5 发热高、耗电快
现象:推流 5 分钟后机身明显发热,帧率开始掉,播放端看到画面变卡。被测试的设备是普通手机而非专门编码卡。
原因:最常见的是把屏幕采集和编码全跑在主线程,CPU 压力大;另一个原因是帧率设太高,1080p 60fps 的编码负载远超同屏所需。很多组件默认参数是按直播场景配置的,没有针对同屏做减配,需要手动降帧降分辨率。
解决:先把帧率从 60 降到 15,分辨率从 1080p 降到 720p,延迟和发热都会明显改善。再把编码器和虚拟显示创建移到独立线程,主线程只做 UI 操作。另外检查组件是否支持屏幕静止时自动降码率——同屏场景大量时间是静态界面,组件如果能检测场景静止并自动降码,发热和带宽都会降一个台阶。
6. 把推流组件真正接入业务:延迟实测与自动重连
6.1 用本地回环实测延迟,别靠感觉
延迟数值必须实测,不能靠肉眼看“看起来不卡”就宣发上线。我的做法是用同一台设备做回环测试:被推流端放一个毫秒计时器,播放端用同一局域网的电脑打开 VLC 拉流,再用手机拍两个屏幕的照片,把计时器时间差算出来。
重复测 5 次取平均值,低于 300 毫秒基本合格,300 到 500 毫秒能接受,超过 500 毫秒就要回去调缓冲。测的时候要保证是无线网络环境,模拟真实使用场景。如果播放器缓冲开得太大,实测延迟会异常高,先把 VLC 的缓冲参数压下来再测,否则测试结果不具备参考价值。
6.2 断线自动重连:给推流加上后悔药
同屏业务里网络抖动不可避免,掉线之后能不能自动恢复,直接决定体验。我在接入业务时会在推流器外层包一层重连管理器:推流失败或 Socket 异常断开,延迟 1 秒、2 秒、4 秒地指数退避重试,最多尝试 5 次。重连成功后重新做一次 RTSP 握手,不需要重启整个采集会话——屏幕采集和编码可以继续复用,只需要重建 RTSP 会话和 RTP 发送通道。这样能把重连时间控制在秒级,用户感知很弱。注意重连时要把编码器强制输出一个关键帧,让新会话的播放器能立刻出图。
6.3 一个习惯:上线前强制走一遍丢包验证
从第一次接 EasyScreenLive 这类组件到现在,我养成了一个习惯:每次上线前强制走一遍丢包验证。方法很简单,用一条网线加上限速工具,把丢包率调到 3%、5%、10% 分别测一轮,看播放端的延迟曲线和花屏恢复时间。很多参数调优时没暴露的问题,在这种压力测试下都会现形——码率控制不当、PLI 没处理、重连逻辑不完善,一次全暴露出来。从那以后,我每次接入推流模块都会先做这套验证,再谈上线。希望帮到你。
本文还有配套的精品资源,点击获取