资讯动态

同屏推流实战:从RTSP/RTMP选型到EasyScreenLive调优

发布时间:2026/10/15 18:20:19 来源:尧图企业网站定制
简介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、PLAYRTSP 的会话流程是固定的四步任何一步返回错误码都会导致推流失败。最常用的推流流向是组件作为客户端把编码数据推给 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_port5004-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 编码负载和码率双高发热严重反而掉帧我一般建议 720p15fps 作为默认。操作细节看不清再升到 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 丢包后最简单的恢复策略是等播放器回传 PLIPicture Loss Indication收到后编码器立即强制输出一个 IDR 帧。很多组件的“画面花屏几秒不动”问题就是没处理 PLI或者处理了但间隔太长。这一项要在联调时专门验证做一次丢包测试比调半天参数都管用。下表是六个参数的推荐起点按同屏场景默认值整理参数推荐值适用场景备注码率模式CBR弱网或固定带宽画质优先改 VBR目标码率2 Mbps720p15fps画面复杂提到 4 MbpsI 帧间隔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 没处理、重连逻辑不完善一次全暴露出来。从那以后我每次接入推流模块都会先做这套验证再谈上线。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑