做监控接入这些年,我发现一个很有意思的现象:只要你手里的项目同时出现“GB28181”和“WebRTC”两个词,那基本就是同一个套路——前端设备都在走国标SIP信令和RTP媒体流,但最终用户端的画面播放却往往要落到浏览器、小程序或者App里,而浏览器不会认识GB28181那套PS封装流。这套C/C++源码程序,说白了就是把摄像机现有的国标接入能力,重新接出一条能通向WebRTC的低延时通道,同时保留完整信令控制能力。

它更像是一个可落地的“中间件+接入网关”,负责完成设备注册、目录查询、实时视频点播、云台控制、语音对讲、报警上报这些GB28181标准动作,再把拿到手的RTP/PS码流解包、重组,按WebRTC需要的封装格式播放出去。如果你想做的是把一堆国标摄像机接进自研平台,又不想被厂商私有SDK绑死,同时还希望网页端能流畅看流,那这套源码的参考价值非常大。下面我会直接从源码结构和实际联调角度来聊一聊,尽量把每一个模块为什么这么设计都讲明白。

1. 源码想解决的事与整体架构思路

1.1 国标摄像机与WebRTC客户端之间的断层

先说一个很多人第一次接触国标项目时容易踩的误区:GB28181不等于“直接把RTSP拉流改成国标拉流”。GB28181体系下,设备向上级平台主动注册,平台向设备发起INVITE会话请求,设备然后把视频用RTP打包,常见的是把H.264/H.265码流先封装成PS格式,再放到RTP payload里推给平台。在这个过程里,SIP负责控制,RTP负责媒体,二者是强绑定的。

而WebRTC那一边完全是另一套思路:浏览器要的是SDP协商、ICE连接、DTLS加密,媒体封装通常是RTP直接携带H.264 NAL单元,还附带NACK、RTCP反馈、Jitter Buffer等一套实时抗弱网机制。你让用户去装浏览器插件不现实,让设备直接支持WebRTC也不现实,所以必须在中间放一层翻译器,把“国标侧的信令和媒体”转成“WebRTC侧的信令和媒体”。这个翻译器用C/C++写,就比用Java或者脚本语言要实在得多,因为媒体通道对内存拷贝、字节序处理、并发调度要求都很高。

在源码结构上,我发现最合理的做法是分成三个相对独立的子系统:

  • 国标SIP信令处理子系统,负责注册、心跳、目录、INVITE、BYE、设备控制等。
  • 流媒体接收和解封装模块,负责监听媒体端口,接收RTP包、解析PS封装、拆出H.264/H.265编码帧。
  • WebRTC出口子系统,负责和浏览器端交互,完成SDP/ICE协商,并把媒体重新封装后通过WebRTC的传输层送出去。

这三个系统不要混在一个线程里,不然后面调试会非常痛苦。源代码最初能理顺,也是因为一开始就把控制面和媒体面拆开了,日志也能分开打,联调时能快速定位问题到底出在SIP还是RTP,还是WebRTC协商阶段。

1.2 为什么选择C/C++来做国标接入链路

这个问题几乎每次给客户讲代码时都会被问到。国标接入不是说不能用Java或Go写,实际上很多上层平台就是Java写的,只把媒体处理下沉到C++服务。但如果你想交付的是一套“源码级”的接入能力,C/C++仍然是最合适的选择。

理由其实很现实。第一,GB28181的媒体处理经常要面对高并发和多路码流,一路1080P的H.264码流大概是4到8Mbps,一个中型项目动辄几百路并发,每路都要做解PS、组帧、转封装、抖动缓冲,脚本语言在这种场景下要么GC不稳定,要么内存占用成倍上涨。第二,C/C++可以非常方便地调用eXosip、FFmpeg、libsrtp这些底层库,直接拿到内存指针做零拷贝,减少一次内存复制就是减少一次延时。第三,这套源码经常要跑在嵌入式设备或者边缘网关里,资源受限,C/C++编译产物体积和依赖都可以精确控制。

当然,纯C写这种项目也不是不行,但如果代码规模上了万行,我还是建议用C++封装一层。把每个信令会话抽象成类,把RTP包缓存做成对象池,把回调函数包装成事件广播,代码可读性会好很多。实际写的时候也不用搞特别复杂的C++特性,用上类继承、智能指针、std::function已经足够了。过度设计反而是给自己埋坑。

还有一点容易被忽略:C/C++代码在Windows和Linux之间移植相对方便。很多客户的服务器是CentOS或者Ubuntu,但研发环境可能有人用Windows,源码里如果使用CMake管理工程,再配合VSCode配置C/C++环境,基本可以做到同一套代码在两边编译。遇到跨平台问题时,主要把socket、线程库的差异隔离到一个公共层里,后面就不会处处写#ifdef。

2. 源码里的核心模块,先建立一张代码地图

2.1 SIP信令收发模块

信令部分是整个国标对接的“大脑”。我自己处理过很多项目,凡是注册不上、目录拉不到、点播不响应,十有八九都出在SIP信令交互的细节上。GB28181的SIP并不是标准SIP的简单套用,它在SIP消息体里塞入了自己的XML控制指令,并且要求特定的UA编码格式和设备ID格式,比如20位设备编码、SIP服务器地址、端口、密码认证等。

在C/C++实现里,常见做法是直接基于eXosip库,也有团队自己封装osip层。eXosip的好处是已经帮我们处理了SIP事务状态机、重传机制、鉴权流程,我们只需要关心业务事件回调。代码里通常会有几个注册相关的接口:设备注册、注销、周期心跳、目录查询请求。听起来很简单,但真正实现时要注意几点。

注册过程中发送的REGISTER消息里,From和To的URI必须是设备编码构成的SIP URI,Contact头域也要带上可访问的IP和端口,Expires不能设置得太短,否则平台会觉得设备不稳定。心跳用的是MESSAGE方法,Content-Type是Application/MANSCDP+xml,命令类型为Keepalive。很多刚接触的人会把心跳消息里的XML写错,导致平台已经注册成功,但过一会就标记设备离线,这种问题很难从表面上看出来,必须抓包对比报文。

另外,国标里还有Catalog目录查询和DeviceControl控制指令,这些消息也走SIP MESSAGE,但是XML里的CmdType不同。收到查询指令后,设备要回一个包含通道信息的XML响应。代码里这部分通常用XML解析库读取,尽量避免直接字符串匹配,因为不同厂商在XML的字段顺序上可能做调整,但标准名称不会变。

源码里一个重要的设计点是信令回调要尽快返回。如果你在收到平台请求后去做同步磁盘操作或者网络请求,很容易导致SIP事务超时。我们一般把收到的事件投递到工作线程池,回调函数里只做入队和轻量解析。这个原则看着简单,真到一套代码里容易写着写着就乱了。

2.2 RTP/PS流接收与解封装

信令建立起来以后,真正的压力在媒体面。回想一下最典型的点播流程:平台向设备发起INVITE,SDP里要求接收PS封装的视频流,设备随后往平台指定的IP和端口发RTP包。这些RTP包负载不是裸H.264,而是PS格式。如果SDP里协商的媒体类型是PS,那RTP包里往往是音频和视频混合封装在同一个PS流里,需要通过PS包头里的stream_id区分是视频PES还是音频PES。

这就到了代码里比较核心的部分:RTP接收线程在收到包之后,第一步要解析RTP头,检查sequence和timestamp是否连续,不连续要记录丢包。然后,把RTP payload放到PS缓存里。PS封装里有几个关键起始码: 00 00 01 BA 是系统头, 00 00 01 BC 是节目流映射, 00 00 01 E0 是视频PES, 00 00 01 C0 是音频PES。实际解析时不要只找E0,因为有些编码流里会出现多个E0包,一定要结合PES头长度和payload开始标志位来判断。

把视频PES解出来后,里面是完整或分片的H.264/H.265接入单元。标准情况下PES payload以 00 00 00 01 的NAL起始码开头,如果有多个NAL单元就会有一个IDR帧加SPS/PPS。我们需要把这一整段数据取出来,交给下游模块。很多做WebRTC的同事刚开始不太理解这一步的复杂度,因为WebRTC收到裸H.264时,需要的是用RTP分片,其中SPS/PPS要么通过带外方式传递,要么在关键帧前带内发送。国标过来的PS流通常每个关键帧都会带SPS/PPS,但也不排除某些设备只在会话开始时发一次。

我记得最早调试的时候,画面一直黑屏,后来抓包才发现设备的PS流里,SPS/PPS只在第一个关键帧里出现了一次,而WebRTC那边拿到关键帧时还没完成解码器初始化,后面收到的P帧自然解不出画面。解决办法是从PS流里单独把SPS/PPS抽取出来缓存,再在WebRTC的RTP打包时按需插入。

媒体面代码还要处理时间戳同步问题。GB28181的RTP时间戳单位通常是90000Hz,但多个通道如果是音频视频混合在一个PS里,时间戳可能不是均匀递增。WebRTC要求音视频有自己的时间线,所以中间层最好把解包后的每一帧都打上自己的pts和dts,不然后面音频对讲和视频录制都会出现音画不同步。这套源码里专门维护了一个帧队列,用单调时钟和RTP时间戳做映射,实测下来多路并发时比直接透传RTP时间戳稳定得多。

2.3 WebRTC推流与拉流模块

如果说信令是控制面,媒体接收是数据面,那WebRTC模块就是这整套代码的“最后一公里”。目前网页端访问摄像头最理想的方式就是WebRTC推流和拉流。拉流很好理解,用户打开页面,平台把摄像头的实时画面推到浏览器;推流一般用于对讲或者手机端把本地画面传给平台再转给其他端。

WebRTC模块在C/C++里最烦的不是SDP交换,而是ICE和DTLS。国标平台内部网络相对固定,很多设备也没有公网IP,所以并不需要特别复杂的NAT穿越,但浏览器端可能处于各种网络环境,网关模块自身需要支持ICE candidate的收集和连通性检测。如果只是局域网演示,可以简化成host candidate,但如果要公网访问,必须考虑STUN,甚至需要把媒体端口映射到公网。

SDP协商这块,建议保持标准WebRTC语义。浏览器发起offer的时候,我们作为answer方返回服务端的SDP,其中要包含服务端能接收的媒体格式、ICE信息、DTLS指纹。在实际项目中,很多人喜欢拿WebSocket自己写一套信令,这完全可行,只要双方定好SDP交换流程即可。源码里更推荐使用JSON格式包一层,里面包含type和sdp字段,后续增加控制指令也方便。

WebRTC的媒体出口有一个关键设计点:不要在国标RTP的基础上直接改头转发。曾经有个版本为了省CPU,把国标侧收到的RTP直接改SSRC和时间戳后塞进WebRTC,结果浏览器端20秒后必定花屏或者卡住。原因很简单,WebRTC对RTP序号、时间戳的连续性、RTCP反馈要求很严格。国标侧RTP序列号经常受网络抖动影响,而WebRTC有自己的丢包重传和Jitter Buffer,如果直接转发,这些机制等于没起作用。正确做法是解出帧后再按WebRTC的H.264 RTP打包规范重新发送,处理好STAP-A和FU-A分包,单帧太大时分片,小NAL单元尽量合并减少包数。

音频对讲模块也放在这里。当用户按住对讲按钮时,浏览器采集到的麦克风音频先由WebRTC发送到服务端,服务端把它解码成PCM,再编码成国标设备能接受的G.711或AAC,然后封装成RTP发往设备。对讲能不能通,多半取决于SDP协商时是否带上了音频媒体。GB28181的语音对讲流程和普通的Invite点播有一些差异,需要开启双向媒体,而且要确认设备侧的RTP端口是固定的还是从SDP里带出来的,否则音频发过去设备不处理。

我之前遇到一个奇怪的问题:视频正常,对讲没声音。抓包发现服务端把音频RTP发给了设备的SIP信令端口,因为这个设备没有在SDP响应里明确指定音频端口,我只用了默认值,后来重新解析SDP里的c行和m行才算解决。这种问题没有标准答案,必须按设备实际报文来适配。

3. 从源码编译到第一个拉流画面的实操过程

3.1 依赖库和编译环境

拿到源码后,第一步不是看代码,而是先把依赖确认清楚。我建议在Linux下编译和运行,简单省事。项目依赖可能包括libosip2、libeXosip2、FFmpeg、libsrtp、openssl,如果做音频处理还可能要opus库。CMakeLists可以先规划成下面这样:

cmake_minimum_required(VERSION 3.16)
project(gb28181_webrtc CXX)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(PkgConfig REQUIRED)
pkg_check_modules(EOSIP REQUIRED IMPORTED_TARGET eXosip2)
pkg_check_modules(OSIP REQUIRED IMPORTED_TARGET osip2)
find_package(FFmpeg REQUIRED COMPONENTS avcodec avformat avutil swscale)

add_executable(gb28181_gateway
    src/main.cpp
    src/sip/sip_stack.cpp
    src/media/ps_demux.cpp
    src/media/rtp_receiver.cpp
    src/webrtc/rtc_session.cpp
)

target_link_libraries(gb28181_gateway
    PkgConfig::EOSIP
    PkgConfig::OSIP
    FFmpeg::avcodec
    FFmpeg::avformat
)

在Windows上编译就把这些依赖换成对应的源码编译版本即可,不过我还是建议先在一台Linux服务器上编译通过,再去处理跨平台问题。VSCode配置C/C++环境时,要注意includePath和c_cpp_properties.json里的编译器路径,用CMake插件生成缓存后,代码跳转和调试会方便很多。真到了跑起来的时候,个人建议先关掉防火墙,或者直接放行UDP/TCP对应的信令和媒体端口,很多项目最初调不通就是因为防火墙把SIP响应丢了。可以先用一个简单命令验证端口是否开放:

nc -uvz 192.168.1.100 5060
nc -uvz 192.168.1.100 9000

如果这条命令通不过,后面所有SIP报文都可能被系统静默丢弃,而且不容易发现。

3.2 注册与保活信令的报文对照

程序跑起来后,你马上需要抓包验证。这里有一个很值得养成的习惯:不要只在平台端看日志,更不要只知道抱怨设备没响应,真正有效的办法是两边同时抓包,然后对照SIP报文集逐条分析。抓包命令我喜欢用tcpdump:

tcpdump -i eth0 udp port 5060 -n -X -s 0

正常注册报文应该长这样,设备侧主动发REGISTER:

REGISTER sip:192.168.1.100:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.20:5060;rport;branch=z9hG4bK123456
From: <sip:34020000002000000001@192.168.1.100>;tag=102
To: <sip:34020000002000000001@192.168.1.100>
Call-ID: 1234567890@192.168.1.20
CSeq: 1 REGISTER
Contact: <sip:34020000002000000001@192.168.1.20:5060>
Expires: 3600
Content-Length: 0

这里设备编码是34020000002000000001,SIP服务器地址是192.168.1.100。平台收到后如果鉴权通过,会返回200 OK,然后设备按固定周期发送MESSAGE消息做心跳。心跳XML体里不能漏掉CmdType、SN和DeviceID,否则平台可能识别不出。

有时候平台会返回401,让设备带Authorization字段重新注册。这个鉴权机制在很多二次开发项目里被简化处理了,但如果是自己写完整源码,建议用eXosip自带的鉴权回调,不要把密码硬编码在报文里。源码里还会有一个线程专门检查最近一次心跳时间,如果超过N秒没收到平台的任何消息,就认为注册已经失效,需要重新REGISTER。这个“N秒”要根据平台设置动态调整,最快的平台可能90秒无心跳就判离线,稳妥的做法是心跳间隔设30到60秒。

3.3 实时点播流程,从INVITE到WebRTC播放

点播流程是整套源码里最复杂的部分,也是最容易出问题的地方。简单来说,平台先向设备发INVITE。SDP请求的会话名如果是“Play”,说明这是实时点播;如果带上“Playback”参数,说明要历史回放。大多数调试可以先只做实时点播,代码路径更短。

平台侧发过来的SDP大概长这样:

v=0
o=34020000002000000001 0 0 IN IP4 192.168.1.100
s=Play
c=IN IP4 192.168.1.100
t=0 0
m=video 9000 RTP/AVP 96
a=sendrecv
a=rtpmap:96 PS/90000
y=0100000001

这个SDP里的关键信息是视频媒体端口9000和负载类型96。设备收到以后,如果同意,就会往192.168.1.100的9000端口发送RTP/PS流。源码里要做的下一件事是解析这个SDP,提取出媒体端口、ip、编码类型。此时必须注意,RTP的SSRC不仅仅是随机数,国标里SDP的y字段会直接指定媒体流标识。例如 y=0100000001 前几位含义和平台编码有关,很多协议栈会拿这个字段生成SSRC。细节上不能偷懒,否则有些设备会把流发到别的端口。

INVITE会话建立以后,媒体接收线程要立刻启动,对RTP包做缓存和重组。这里我建议在代码里增加一个“首帧超时提示”:如果SIP已经收到200 OK,但5秒内没有收到有效的RTP包,就打印一条告警。很多联调问题靠它就能快速定位。等RTP流稳定进来后,PS解析模块会输出H.264帧,WebRTC模块然后与浏览器完成握手,用户就能看到画面了。

需要注意的还有摄像头通道选择。平台先发Catalog查询,拿到每个通道的DeviceID和通道状态;然后真正点播时,INVITE的Subject字段里要带上这个通道编码。很多设备并不只一路视频,如果Subject填错,可能收到设备返回的488或者直接无响应。源码里最好把Catalog查询结果缓存下来,点播时直接从内存里拿通道信息,不要每次临时拼接。

4. 高频问题与排查技巧实录

4.1 国标对接请求超时到底是谁的锅

“请求超时”这四个字应该是做国标接入时遇到最多的报错。不同平台上提示五花八门,有的是SIP层直接超时,有的是平台业务层报“设备无响应”,但你真正去查时会发现场景差异很大。我这几年遇到的请求超时,几乎都集中在下面几个环节。

第一,注册阶段超时。设备发出REGISTER以后如石沉大海,很有可能平台SIP服务器地址配置错误,或者服务器的5060端口根本没监听。还有一种是比较隐蔽的:服务器监听的是TCP,而设备配置或源码里默认走了UDP,两边传输协议不一致,REGISTER包虽然到了服务器,但服务器的响应回不到设备。此时抓包是最直接的判断办法。

第二,目录查询超时。平台发MESSAGE给设备查目录,设备却迟迟不回复。问题经常出在设备没有正确解析XML,或者设备正忙、SIP栈不支持并发事务。我们在源码里会维护一个pending请求表,超过3秒就自动重发一次,但重发逻辑要遵守SIP的定时器规范,不能无限快速重发,否则会让平台认为设备攻击服务器。正常的做法是先重发一次,等更长超时时间,如果还失败就上报业务层,由人工决定是否继续尝试。

第三,INVITE点播超时。平台向设备发起点播,设备可能返回错误码或者直接不响应。最常见的情况是设备端已经超过最大并发数。比如有些旧款摄像头只支持两路预览,如果你已经通过其他平台占用了两路,那新的INVITE自然会被拒绝。排查时可先降低并发,或者看看设备的状态灯和本地预览是否异常。

下面这个表格是我整理的最常见几类问题,每次联调我都会对着过一遍:

故障现象 最常见原因 第一排查手段
REGISTER无响应 端口不通/协议不对/IP配置错误 抓包看是否有UDP回包
注册成功但平台显示离线 心跳XML格式错误或心跳间隔太长 查看平台在线状态阈值
Catalog查询超时 设备通道数量多,设备处理慢 抓包看是否收到MESSAGE请求
INVITE点播无媒体流 SDP媒体端口被防火墙挡住 检查平台监听端口是否收到RTP
收到RTP但无画面 PS解析失败或SPS/PPS缺失 保存一段RTP转存后离线分析

4.2 用报文分析解决最头疼的对讲问题

语音对讲是国标对接源码里比视频点播更容易“翻车”的功能。首先是概念上的差别:点播是单向拉流,对讲却是双向实时通信,服务端既要收设备的声音,又要将客户端采集的声音反向送给设备。如果你在SDP协商时只写了 a=recvonly ,那设备理所当然不会收你的音频RTP。

对讲调试时,我第一个动作是抓包看SDP里音频那一行的内容。很多国标设备默认支持音频的payload类型是8,也就是G.711A。如果你的服务端只协商了opus编码,设备可能不认识,直接忽略音频媒体。这里通常需要做一个音频编码转换:把浏览器来的opus或PCM解码,再编码成PCMA/PCMU,按照每20毫秒一个RTP包发给设备。常用的处理链路是FFmpeg的resample和编码接口。有一个细节非常值得注意,对讲发往设备的RTP时间戳必须连续递增,一旦出现长跳变或者回退,设备侧会表现为声音断续、爆音甚至直接不出声。

如果对讲只听到自己说话,而对端没声音,往往是RTP的发送源端口和设备期待的不一致。有些设备在收到INVITE的200 OK后,会锁定SDP里连接地址对应的端口作为音频接收端口。服务端发送音频时如果用临时端口发,很容易被设备丢弃。解决办法是让对讲的音频RTP从和SIP/设备协商一致的固定端口发出,源端口不要随意切换。项目里可以在RtpSender里绑定发送socket到本地某个固定端口,并保证INVITE的SDP里填的就是这个端口。

4.3 WebRTC弱网卡顿的优化切入口

公网或者Wi-Fi环境下的WebRTC弱网优化,是另一个被问得特别多的话题。摄像机码流一般码率比较高,尤其H.265编码在某些设备上为了保清晰度,码率甚至会飙到8Mbps以上,在弱网下不做任何处理就推到浏览器,结果只能是卡成幻灯片。

实际上,WebRTC本身具有不错的抗丢包机制,前提是媒体发送端要启用NACK和RTCP反馈。如果代码里直接禁用了NACK,或者没有正确发送RTCP Receiver Report,浏览器端的抖动缓冲就失去了丢包感知能力。源码里建议把WebRTC的拥塞控制策略设为可调节,在网络变差时优先降低分辨率而不是帧率,这样用户至少还能看清画面内容。

还有一个优化点是把I帧间隔调整到和国标设备侧编码参数匹配。很多国标设备默认的I帧间隔是50帧甚至100帧,当浏览器端发生关键帧丢失时,画面要等很久才能恢复。解决办法是在WebRTC出口模块收到关键帧后,立刻把SPS/PPS信息随关键帧发送,同时在浏览器端可以做关键帧请求。如果网络质量持续不好,可以尝试旁路让FFmpeg做一次转码,输出较低码率的H.264流给WebRTC。这个操作会增加CPU占用,但换来的流畅度提升非常明显。

5. 工程化落地中的一些个人体会

5.1 线程模型和资源管理不能临时凑合

如果你打算把这套源码真正用在生产环境,我强烈建议先花时间把线程模型梳理清楚。最忌讳的是每来一路视频就new一个线程,几百路下来线程切换开销都能把CPU吃满。实际工程里常用的方式是“接收线程只做接收和最小值解析”,把数据投递到一个有界队列,然后由一组处理线程消费并完成PS解封装、帧重组。这样做的好处是队列长度和消费者数量都可以动态调整,某一帧解析耗时较久时也不会阻塞RTP接收线程。

C++的内存管理也要格外小心。RTP包缓存特别容易造成内存碎片,建议写成对象池,提前分配足够多的MediaPacket对象,使用完后放回池里。如果频繁malloc/free大块内存,运行几小时后系统内存碎片会非常可观,尤其嵌入式设备上体现更明显。

5.2 做好日志和状态导出,比多写十个功能更值钱

我特别想强调一个很朴素的工程经验:国标设备厂商之间的兼容性问题,光靠文档是看不出来的,必须靠日志去现场排查。源码里最好从一开始就设计好分级日志,控制面和媒体面分开记录,并且能输出最近N条信令报文摘要。这样客户反馈问题时,你不用远程连到设备上也能先判断出大概环节。

可以给服务加一个HTTP状态接口,输出当前注册的设备列表、在线状态、每一路会话的SIP状态、媒体接收速率、丢包率、WebRTC连接状态。有一次客户报“视频时不时断开”,我们通过状态接口发现某一路的RTCP接收间隔异常,最终定位到交换机端口协商问题,这类问题如果没有状态数据,排查难度会大很多。

5.3 从兼容一台设备到兼容一批设备的经验

最后再分享一个这些年反复遇到的现象:兼容一个品牌容易,兼容所有设备难。国标标准虽然统一,但厂商在PS封装细节、SIP域字段、心跳XML处理、音频格式上都有自己的理解。源码里做硬件适配时,一定要保留一个“设备能力配置”层,把每个型号的差异通过配置文件或数据库隔离起来,不要为临时适配写死在代码里。比如有的老设备音频只支持PCMU,有的只支持PCMA,有的对讲要求DTMF按键事件,有的却直接走音频流。没有配置层的话,每接一个新设备就要改一次源码,长期维护成本非常可观。

从协议标准来看,国标仍然会长时间存在,WebRTC也会在浏览器端持续占领份额。这套源码本质上是在标准协议和现代Web能力之间搭了一座桥,你越理解其中信令和媒体各层的边界,越能在真实项目中举一反三。后面如果再碰到设备不上线、有流无画面、声音不通这类问题,建议先从报文和端口入手,一层层剥洋葱,而不是盲目改代码。

Logo

邀请您加入社区

更多推荐