PS流深度解析:从RTP打包到H.264帧提取的完整实践

发布时间:2026/10/17 23:06:23
PS流深度解析:从RTP打包到H.264帧提取的完整实践 简介面向音视频流媒体开发者这份压缩包提供了海康、国标PS流解析的两种实现方案——基于ffmpeg的解析版本与直接解析PS流版本适合需要对接海康设备或国标PS流做二次开发、播放与转码的技术人员。压缩包共27个文件包括6个C源文件、5个头文件以及Visual Studio解决方案与工程文件sln、vcxproj、filters和调试过程生成的obj、pdb等整体仅2.24MB代码精简便于直接阅读和编译调试。已有1096人学习下载。内容包含两套可对照的工程ffmpeg_demux_ps演示如何用ffmpeg API解封装并提取帧信息直接解析版本则提供PsAnaly.h与test_PsAnaly.cpp不依赖ffmpeg手动解析PS流结构可获取PTS/DTS、分离音视频数据包并附有解析笔记能帮助读者从容器格式到具体解码流程建立完整认知是研究流媒体协议实现与优化的重要参考资料。1. 拉流却花屏PS流为什么是安防音视频的第一道门槛从某主流安防厂商的摄像头拉流用 FFmpeg 转封装成 MP4结果视频花屏、没有声音。打开抓包文件一看满屏000001BA——这不是 H.264 的00000001起始码也不是 MP4 的ftypbox而是 PS 流的 Pack 头。安防设备的音视频流媒体输出默认不走 MP4 也不走 TS而是把 H.264 帧装进 PS 流再用 RTP 推到平台侧。解析不好这一层后面的转码、录像、AI 分析都无从落地。搞懂 PS 流的层级和时间戳体系是进入流媒体对接深水区的第一道门槛也是这份资源里最值得抠透的底层知识。2. 起始码与层级PS流的骨架是怎么撑起来的PS 流Program Stream节目流是为可靠传输设计的复用格式。它不是一个裸编码帧的简单拼接而是由四种基本单元按严格层级组成Pack 头、系统头、PSM节目流映射、PES 包。国标 PS 流在结构上比 MPEG-TS 简单但细节陷阱反而更多。2.1 三个起始码定江山Pack、System Header、PSM整个 PS 流的自同步完全靠起始码。所有的单元都从00 00 01开始第四个字节区分类型。解析器的第一件事不是解码而是找到并确认这些起始码起始码对应单元作用00 00 01 BAPack Header包起点携带 SCR 系统时钟00 00 01 BBSystem Header描述码率、视频/音频流上限可以缺省00 00 01 BCPSM流 ID 与编码类型的映射表00 00 01 C0~CFAudio PES音频帧载体常见0xC000 00 01 E0~EFVideo PES视频帧载体常见0xE000 00 01 BD私有流厂商私有扩展数据结构化理解一个 PS 包最外层是 Pack Header它后面可以跟系统头、PSM然后跟若干个 PES 包。PSM 起到的是「节目映射表」作用告诉解析器0xE0是 H.264、0xC0是 G.711 还是 AAC。问题在于PSM 不像 TS 里的 PMT 那样周期性必发——不少设备只在第一个 I 帧前发一次 PSM甚至从不发。解析器如果一进来就等 PSM等不到就直接卡死。常见的实现思路是PSM 出现就更新映射表没出现就按已知默认值0xE0视频0xC0音频继续解析。Pack 头本身也有讲究。BA后面的 6 个字节里藏着 SCRSystem Clock Reference它被分成三段存储分别落在不同的字节位里。我们做解析时一般不抠 SCR 的位级拼接因为后面 PES 头里的 PTS/DTS 才是驱动渲染的关键SCR 更多是给系统时钟同步用。但解析 Pack 头时至少要正确跳过它的变长扩展段——BA后第 4 字节的高 2 位决定是否有扩展段扩展段连接字段复杂容易踩坑。2.2 PES 是中间人从 0xE0 / 0xC0 到一帧编码数据PESPacketized Elementary Stream是 PS 流的第二层也是离编码数据最近的一层。一个 PES 包的典型布局是00 00 01stream_id 2 字节包长 PES 头标志区 可选扩展字段 编码负载。视频stream_id是0xE0音频是0xC0。负载部分按约定应该是一个完整的 Access Unit即一个编码帧比如一个 H.264 的 IDR 帧或非 IDR 帧。但要注意很多厂商设备会在 IDR 帧的 PES 负载前面直接拼上 SPS/PPS等于每遇到一个 IDR 帧你都能在 PES 负载里找到三个 NAL 单元而不是一个。这是设备层的「省事」实现也意味着解析时不能理所当然地把「一个 PES 等于一个视频帧」。包长的处理是另一个隐藏问题。PES 头里 2 字节包长指向 PES 负载的字节总数。标准允许包长为 0表示长度不限定——这个设计本意是针对某些私有流。可实际抓到的海量设备流里视频和音频 PES 也出现过包长为 0 的情况。一旦遇到如果解析器还把包长当作跳跃依据整个后续数据全错位。我一般会这么设计包长字段只用作预校验真正的 PES 边界必须靠下一个起始码探测来确认两者结合才稳。2.3 时间戳三角SCR、PTS、DTS 谁负责什么PS 流里存在三套时间生产源很多人在这里栽跟头。SCR 位于 Pack 头是系统时钟参考5 字节位拼接单位是 90kHz表示当前 PS 包送达系统时的期望时钟值。通常流媒体平台不会直接把 SCR 当渲染时钟因为 Pack 头的到达时刻受 RTP 网络抖动影响很大。PTS 和 DTS 都在 PES 头里单位同样是 90kHz。PTS 表示这个帧应该被呈现的时间DTS 表示解码时间。对没有 B 帧的编码流PTS 等于 DTS一旦 B 帧出现DTS 会比 PTS 提前因为解码器必须先解码后面的参考帧。很多解析器实现只读 PTS不考虑 DTS这在直播预览场景没问题可一旦做录像回放或抽帧分析解出的帧序就是错的。需要注意 PES 头里的时间戳编码不是简单的 4 字节 int而是 5 字节、33 位值均匀塞在 5 个字节的若干位上。拼位时容易出错源码里通常会有这样一段宏// 从 5 字节 PES 时间戳字段拼接出 33 位 90kHz 值 #define PES_PTS(hdr) \ (((uint64_t)((hdr)[0] 0x0E) 29) | \ ((uint64_t)((hdr)[1] 0xFF) 22) | \ ((uint64_t)((hdr)[2] 0xFE) 14) | \ ((uint64_t)((hdr)[3] 0xFF) 7) | \ ((uint64_t)((hdr)[4] 0xFE) 1))这组位操作的含义是33 位 PTS 被拆成 38787 五段分布在 5 个字节中。为什么这样设计因为 MPEG-2 体系里所有时间戳都塞进 5 字节低位丢弃 1 位以保证偶校验对齐。做解析时一定要先处理成 uint64_t 再算差值不能按 int32 比较否则回绕问题会出现在播放几分钟之后。3. 国标承载RTP 怎么把 PS 流塞进流媒体管道国标 GB/T 28181 的信令走 SIP媒体走 RTP。媒体面最核心的一条规则是RTP 的 payload 直接就是 PS 流没有中间的 TS 封装。这个选择背后是带宽和实时性的权衡也决定了解析器的入口形态。3.1 为什么选 PS 而不是 TSTSTransport Stream每个包固定 188 字节设计目标是有损信道下的随机接入——丢了包还能靠下一个包头的同步恢复。代价是每个 TS 包都要重复带 PID、连续性计数器等头信息带宽浪费约 8%解析时要先做 PAT/PMT 映射再做 PES 重组多一层状态。PS 不需要这些。可变长包、一次承载一整套音视频帧头开销远小于 TS解析时扫描到00 00 01 BA就能建立同步。国标选 PS 的另一个原因是场景差异平台侧大概率是有线内网丢包率可控不需要 TS 那种包级容错而视频录像更需要完整帧边界PS 的「一个 PS 包多帧」结构更适合直接落盘。很多新手用 TS 解析的思路去解析 PS先找固定包长、再找 PID结果发现 PS 根本没有 PID 的概念走了弯路。3.2 RTP 打包 PS 的语义一包一帧还是一包多个 PS 包国标媒体协商里RTP payload type 通常用动态值96时基固定为 90000。常见的打包模式有两种解析器必须兼容。第一种模式是一个 RTP 包承载一个完整 PS 包包边界与 RTP 包边界对齐最容易处理只要按 RTP 序列号排序后直接透传给解析器。第二种模式是一个 PS 包被拆到多个 RTP 包里特别是大码率的 1080p 流单包塞不下。第二种模式要求解析器在 RTP 层做重组装按序列号递增顺序收集载荷直到 PS 包完整。怎么判断一个 RTP 包里哪个位置是 PS 包边界不能靠 RTP 头里的 M 标记位——虽然很多实现约定 M1 表示 PS 包尾但不同厂商的实现并不一致有的设备 M 位含义完全不同。可靠做法是把 RTP 载荷累积到一段缓存里用起始码扫描判断 PS 包是否结束这也是第 4 章状态机的前置条件。RTP 时间戳与 PS 时基同为 90kHz这给时间戳恢复提供了捷径。理想情况下RTP 时间戳可以直接映射为 PTS 基准但注意 RTP 时钟和录像侧时钟可能来自不同晶振长时间运行会有微小漂移。流媒体平台常见的做法是预览用 RTP 时间戳驱动录像时间戳用 PTS 重写兼顾实时性和一致性。3.3 从 SDP 参数到解析器配置抓一个典型的国标设备 SDP媒体行往往长这样mvideo 60000 RTP/AVP 96 artpmap:96 PS/90000 acontrol:videoartpmap:96 PS/90000就是在明确告诉接收端动态负载类型 96 对应 PS 格式时基 90000 赫兹。解析器的初始化参数本质上就是从这里拿到的 payload type 和时基。做调试时第一件事就是确认 SDP 里的 payload type 和实际 RTP 头里的 payload type 是否一致很多平台在m行写的 96码流里却发的是 98这属于协商与媒体不一致的典型翻车点。与之配套的还有arecvonly或asendonly之类的方向属性以及音视频两条 m 行的端口。解析器的资源管理按 SSRC 区分通道同一路视频里语音和图像共用 SSRC 还是分开用 SSRC各厂商风格不一。我一般会在初始化时直接忽略 SSRC 分组让下层按起始码自动识别轨道这样对设备的依赖最小。3.4 厂商实现差异同一协议三种脾气接入设备多了会发现虽然协议写的是同一份国标但实现上差异很大。某厂商的流重视规范性每个 PS 包都包含系统头和 PSM时间戳稳定解析器只要按标准走就不会出错。另一厂商的流骨感得多系统头从头到尾不出现PSM 只在流起始发一次Pack 头的 SCR 甚至恒为 0。还有一种中间派系统头发但不完整PSM 里的 stream_type 与真实编码类型对不上需要靠 PES 负载里 NAL 头的类型去反推编码格式。这些差异不是 bug而是嵌入式平台省资源的策略。写解析器时不要假设每一个字段都可信系统头是参考不是依据PSM 是提示不是承诺真正可信的只有起始码和 NAL 头。解析器状态机的每一分支都应该在「字段缺失、字段为 0、字段非法」三种情况下有自己的默认动作否则接新设备就成了一次次回归测试。4. 状态机与缓冲从字节流到音视频帧的源码级实现PS 解析器本质上是一个带缓冲的有限状态机。这一章给出核心骨架它覆盖了从 RTP 载荷到音视频裸帧的全部路径也是解析类代码里最容易反复改的部分。4.1 数据入口RTP 重组装与累积缓冲RTP 层过来的数据首先进入一个累积缓冲。缓冲不一定要很大但要能处理包边界错位的场景。核心结构是一个 byte buffer 加两个游标filled表示当前已填充字节数head表示当前消费位置。每次 RTP 包到达把 payload 追加到filled位置状态机从head开始消费消费完的数据移动或直接丢弃。struct ps_ring { uint8_t *buf; size_t cap; // 缓冲总容量建议 2MB 以上 size_t filled; // 当前已写入字节数 size_t head; // 当前消费游标 uint64_t rtp_ts; // 最近一个 RTP 包的时间戳 uint32_t rtp_seq; // 最近一个 RTP 包的序列号 };为什么不直接按 PES 包长跳过因为前面说过包长字段可能为 0可能错也可能设备在 PES 负载里混入填充数据。累积缓冲配合起始码探测是最稳的代价是多几十毫秒的延迟对视频平台完全可以接受。2MB 缓冲的余量能覆盖一个 1080p I 帧可能被拆到十几个 RTP 包的情况。4.2 状态机骨架逐字节扫描与消费切换解析主循环对每一个新到字节喂入状态机。三个状态分别对应「找 PS 包边界」「解析 PS 包内头」「消费并分发 PES」enum ps_state { PS_SEEK_PACK, // 等待 00 00 01 BA PS_HEADER, // 处理 Pack/PSM/系统头 PS_PES, // 处理 PES 头 负载 }; static void ps_feed(struct ps_demuxer *dmx, uint8_t c) { switch (dmx-state) { case PS_SEEK_PACK: // 滑动窗口匹配 00 00 01 BA dmx-buf[dmx-filled] c; if (dmx-filled 4) { uint8_t *p dmx-buf dmx-filled - 4; if (p[0] 0 p[1] 0 p[2] 1 p[3] 0xBA) { dmx-head dmx-filled - 4; // 回到包起点 dmx-state PS_HEADER; } } break; case PS_HEADER: // 解析完 Pack 头后遇到 PSM 更新映射遇到 PES 起始码转 PS_PES ... break; case PS_PES: // 解析 PES 头提取 stream_id 与 PTS然后回调裸帧 ... break; } }逐字节喂入看起来笨但这是处理「起始码可能跨界」的唯一可靠方法。RTP 包不会恰好把00 00 01 BA完整放在一个包里跨界是常态而逐字节滑窗天然规避了这个问题。性能上单字节 switch 在嵌入式平台上略吃力可以改成块级扫描——先用 memchr 找 0x00再检查后两字节但逻辑等价。4.3 PES 头解析流 ID、包长、PTS 一起拿进入 PS_PES 状态后先等满 PES 固定头起始码之后至少 9 字节再解析流 ID 和时间戳。这里把长度不定的 PES 头完整解析出来// 解析起始码之后的 PES 头返回 PES 负载起始偏移-1 表示数据不足 static int ps_parse_pes(struct ps_demuxer *dmx, const uint8_t *p, size_t n) { if (n 9) return -1; // 至少要流ID包长头标志 uint8_t sid p[0]; // 0xE0 视频0xC0 音频 uint16_t plen (p[1] 8) | p[2]; uint8_t flags p[4]; // PTS/DTS 标志位 size_t hlen 9 p[5]; // PES 头总长 if (n hlen) return -1; uint64_t pts 0, dts 0; if ((flags 0x80) ! 0) { // PTS 存在 pts PES_PTS(p 6); if ((flags 0x40) ! 0) // DTS 也存在 dts PES_PTS(p 10); } dmx-has_pts (flags 0x80) ! 0; dmx-cur_pts pts; dmx-cur_dts dts; dmx-stream_id sid; // 只有当 plen 合法才用plen 0 时靠起始码探测边界 dmx-payload (uint8_t *)p hlen; dmx-payload_len n - hlen; if (plen 0 plen 3 hlen n) dmx-payload_len plen; // 以声明包长裁剪 return hlen; }这里有个关键取舍p[4]是 PTS_DTS_flags 所在字节但不同设备对这个字节的前缀位本来是10写得不规范有的写00有的写11。正确做法是只按低 6 位判断 PTS/DTS 标志不要校验高 2 位。下面紧接着要考虑 PES 头扩展字段——如果p[5]指示有扩展PTS 的位置会往后移但常见设备流基本不使能扩展出问题时优先怀疑这里。逻辑上为什么plen合法时还要用起始码兜底因为摄像头 SD 卡录像里常出现一种情况PES 头声明的包长大于实际负载多出来的长度被填充字节占满。如果只按包长切会把下一个帧的起始码吃掉。我的习惯是「声明长度优先但边界必须落在下一个起始码之前两者冲突时信起始码」。4.4 时间戳恢复与音视频分轨时间戳恢复不是简单把 PTS 摘出来而是要把它和 RTP 时钟对齐。最稳妥的做法是差分恢复记录上一个 RTP 时间戳与上一个 PTS当新 PES 到来时用 RTP 时间戳差值修正 PTS避免累积漂移// 通过 RTP 时间戳差分修正本地 PTS static uint64_t ts_adjust(uint32_t rtp_ts, uint32_t last_rtp, uint64_t last_pts) { int32_t delta (int32_t)(rtp_ts - last_rtp); // 回绕安全 if (delta -45000 || delta 45000) // 抖动过大 return last_pts; // 保护性丢弃 return (uint64_t)((int64_t)last_pts delta); }int32_t绕回是 90kHz 时基下最容易埋的雷。RTP 时间戳是 32 位无符号约 13 小时回绕一次直接用减法再赋值给 int32_t就同时解决了回绕和抖动过滤。45000 对应 0.5 秒超过这个范围的跳变直接丢弃防止把断流重连的跳帧当成正常帧推给下游。音视频分轨不用复杂的数据结构两个出队队列外加一个回调指针就够struct ps_track { uint8_t stream_id; // 0xE0 / 0xC0 bool got_key; // 是否已遇到关键帧 uint64_t last_pts; void (*on_frame)(uint8_t *data, size_t len, uint64_t pts, uint64_t dts, void *ctx); };got_key的作用是视频轨在遇到第一个 IDR 帧之前别把 P 帧推给解码器否则解码器直接黑屏或花屏音频轨则是got_header的变体G.711 无参数集还好AAC 就要先等 ADTS 头或 ASC。这个字段虽然小却避免了一类非常隐蔽的启动时序问题。5. 常见问题与避坑PS流解析最容易翻车的六个地方解析 PS 流的门槛不在标准文本里而在设备差异里。这里列出接入调试时踩过的六个高频坑按现象、原因、解决三段记录几乎每条都能在某种设备上复现。5.1 起始码跨 RTP 包边界解析器丢了同步现象解析刚开始正常几秒后突然狂报sync lost一查发现00 00 01 BA被 RTP 分包切成了两半。原因设备的 1080p 码流下 I 帧很大一个 PS 包被拆成多个 RTP 包。如果解析器只在 RTP 包内做起始码匹配包边界处的起始码就被漏掉。解决起始码匹配不能限定在单包内要在累积缓冲上做。RTP 层只负责按序列号追加载荷PS 层用滑窗在缓冲上找边界两边解耦。从那以后我做任何解析器都强制要求「边界探测在缓冲上进行接收层永不做格式判断」。5.2 PES 包长写 0按字段跳跃后全部错位现象解析出的视频帧尺寸忽大忽小解码器频繁报 invalid nal,有时能播放但音画不同步。原因设备在 PES 头里填了packet_length 0表示长度未定义。解析器按 0 计算负载长度导致把整个缓冲都当成一个帧喂给了解码器。解决不要用 PES 包长作为 PES 边界用下一个起始码探测作为边界。包长只作为裁剪上限声明长度小于实际可用数据时优先按声明长度切声明为 0 时完全交给状态机的起始码扫描。一句话起始码是唯一的信任源。5.3 PTS 回绕导致时间戳断崖式跳变现象程序稳定运行十几小时后回放时间突然倒退十几小时录像文件索引错乱。原因90kHz 时基下33 位 PTS 约 2.8 小时回绕一次。如果时间戳比较用的还是 int32 或没有做差分计算回绕瞬间看起来就像时间倒流。解决时间戳比较一律转int32_t差值用(int32_t)(cur - last)判断增量而不是直接比大小。同时把 PTS 统一扩到 uint64_t 再写入封装层保证录像文件内部单调递增。5.4 SPS/PPS 不在 PSM 里解码器一直等参数集现象VLC 偶尔能播自己的播放器全黑屏控制台刷一堆no parameter set。原因H.264 的参数集按国标规范应该在 PSM 或单独信令里传递但很多设备直接把 SPS/PPS 塞进 IDR 帧的 PES 负载头部不带任何标记。解析器如果按「PES 负载 一个 NAL 单元」去切就把参数集剥掉了。解决把「PES 负载可能包含多个 NAL 单元」作为默认假设。解析时用 NAL 起始码再次切分负载发现 SPSNAL type 7和 PPSNAL type 8就缓存每次解码关键帧前把缓存的参数集一并交给解码器。本质上是多维护一个「参数集合成」层。5.5 音频 PES 负载为空通道开启后静音现象视频正常音频轨没有任何数据。抓包发现0xC0的 PES 包频繁出现但负载长度只有 0。原因部分设备在双向语音开启前会周期发送空音频 PES 包来维持 RTP 通道的活动状态。解析器把这些空包当正常音频帧送去解码解码器报错后静音。解决PES 负载长度小于最小音频帧长度G.711 为 1 字节时直接跳过不回调同时保持轨道开启状态。顺便建议把「连续多少个空 PES 才关轨」做一次统计避免把短暂的静音误判为断流。5.6 RTP 乱序导致 PS 包拼接错乱现象有丢包时解析器命中错误起始码后面一整个 GOP 报废直到下一个 IDR 才恢复。原因RTP 序列号乱序到达时累积缓冲里 PS 包尾部可能先于头部到位状态机在未对齐位置误匹配到了负载里的00 00 01。解决RTP 层加一个浅排序队列按序列号滑窗排序后再写入 PS 缓冲。队列不需要很深16~32 个包就够代价是要容忍约一个窗口的延迟。丢包时直接丢弃该 GOP 的后续帧等下一个 IDR 重建不要试图用错误数据恢复同步。6. 设计取舍流式状态机与缓冲策略的再思考PS 解析器的设计里最值得反复权衡的不是算法复杂度而是「信任边界」和「内存策略」这两个维度。先说信任边界。整套解析过程中可信度从高到低是起始码 NAL 头 PES 包长 PSM/系统头。起始码是二进制层面的强同步标记只要数据不错位它几乎不会误报NAL 头是编码层信息稳定度次之包长是设备填写的告示牌可能写 0、写错、甚至故意不写PSM 和系统头只是辅助信息。一个稳健的解析器应该把所有「可信任的高位」做成硬逻辑把「不可信任的低位」做成兜底和容错。很多年轻工程师习惯先把标准结构体定义好然后按结构体逐字段读结果遇到一个不按常理出牌的设备就崩。从这次接入的经验看信起始码、防包长、缓存参数集这三个动作能挡住八成以上的兼容性问题。再说内存策略。PS 解析有两种典型的缓冲路线整包缓存和流式透传。整包缓存把一整个 PS 包先收满再解析逻辑简单但要为 1080p I 帧预留 1~2MB 内存流式透传边收边解析内存省却要求回调接口支持分片交付下游解码器不一定接得住。平台侧实现我倾向于折中PS 包按起始码收收满后整包回调但 RTP 层到 PS 层的累积缓冲设上限超过上限强制同步搜索避免内存被异常流打爆。这里有个参数调优的血泪教训缓冲上限不能设太小否则高分辨率低码率设备偶尔出现的大 I 帧会被误杀直接导致连续花屏。最后一个取舍是时间戳策略。标准文档倾向于以 SCR 为准但实际平台都以 RTP 时间戳做基准PTS 只不过是对齐帧序的参照。这个「文档与现实错位」不是实现错误而是 SCR 本质是发送端时钟经过网络抖动到达后已不可信RTP 时间戳虽然也经过网络但它是容器层维护的节奏信号和播放器的时钟模型天然匹配。从那以后我每次接入新设备都会先做一个两分钟的实流测试剥出前 20 个 PS 包的 PTS 和 RTP 时间戳手动画一下关系曲线确认线性关系成立后才写死策略。这条习惯救了我很多次希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询