WebRTC获取GB28181监控摄像头实时音视频流的实现方法

我写了一个网关,核心是打通GB28181和WebRTC,通过浏览器获取监控摄像头的实时音视频流。完全用C/C++实现,依赖极少,效率极高。还封装了一个前端js类,只需要简单几条语句就能在web上拉取摄像头的流。

一、目标和分析

1、在浏览器上直接播放,不用安装任何插件

只能用WebRTC。

如果采用厂商(如海康)的SDK,意味着要安装插件,只能限制在windows下运行。 而WebRTC是现代浏览器的标准。

2、实时性,不能感觉到延时

这里的问题就变成,用什么方式获取摄像头的音视频流?

目前有三种常见方式:

a、厂家专用sdk 本质上私有协议,兼容性差。

b、RTSP协议 优点是接入简单,缺点是几乎没有浏览器可以显示实时 RTSP 协议

c、GB/T.28181即所谓的国标,本质上是SIP协议

这个最佳,媒体采用rtp传送,实时性强。

GB基于sip协议可以充分利用以前开发软交换的经验。 国标开放性也最好,国产视频监控摄像头强制必须支持该标准。

3、在任意操作系统上使用

在某些行业领域,强制安装国信麒麟等国产操作系统, 因此展示监控音视频流的客户端,要求能够在各种操作系统上运行,无论桌面操作系统windows、Linux(包括国信麒麟)、MacOS还是手机操作系统安卓或iOS。

支持WebRTC的现代浏览器,确实可以在任意操作系统上使用。 所以问题变成了,我们实现的网关可以在任意操作系统上部署。跨平台,对第三方库的依赖尽可能少。

4、支持H.264或H.265

最新版的浏览器 Chrome(版本136+以上)、Safari等已经直接支持H.265,Firefox还只能支持H.264。

网关收到呼叫时,如果摄像头的流是H.265,须判断前端浏览器是否支持,如果不支持,则可进行转码。 转码带来的问题是比较费cpu,而且通常会带来2、3秒的延时。

应该尽量避免转码。除了使用支持h.265的浏览器,也可以设置摄像头编码方式为h.264。

5、网关的效率

除了前面讨论的应尽量避免转码,还应支持流分发:

多个前端获取同一摄像头时,应该采用流分发,而非软交换sip通常习惯的发起多个呼叫。 这也是GB/T.28181对媒体服务器的要求。 特别是在转码的情况下,避免了相同流的重复解复用和转码,对资源的节约是很大的。

要能支持多个浏览器拉取多个摄像头的流,网关服务器的cpu占用不高,高效率并发。

二、实现

系统结构:

web浏览器 <--WebRTC--> 网关服务器 <--GB28181--> 监控管理平台(如海康) <--GB28181--> 摄像头

或直接挂摄像头:

web浏览器 <--WebRTC--> 网关(软交换)服务器 <--GB28181--> 摄像头

下面总结一些要点:

1、ps解包

和普通软交换不同,大部分监控摄像头使用GB28181只支持ps封包,ps可以将音频视频封在一起,因此要手写一个高效的解包模块。信令部分也要对摄像头的sdp和信令部分进行处理,以符合国标。

2、WebRTC的音视频流是分开的

采用不同rtp载荷,虽然端口可能复用。

3、发往WebRTC的流封装

对于H.264,须按rfc6184进行封装;

对于h.265,须按rfc7798进行封装(RTP Payload Format for High Efficiency Video Coding (HEVC))。

4、推到浏览器的流要注意时间戳

视频简单起见可以套用ps流的时间戳间隔。

要准确发送rtcp给浏览器。

传送视频帧的rtp,其mark位要准确,简单来说就是相同时间戳的最后一个rtp包mark必须置1,其它都置0。否则浏览器将无法正常播放。

音频的时间戳,应根据采样进行处理,通常都是8000采样率,所以每次加160即可。

5、浏览器信令选择:SIP

WebRTC没有规定控制信令,我采用wss承载的sip协议,sip是久经考验的标准,和GB相同类型。

支持wss-sip协议有开源的jssip,是js实现的完整sip协议栈,很成熟。

6、网关实现尽量减少依赖

网关的sip协议栈,媒体处理都是我自己实现的。

ws/wss协议栈、STUN协议栈、DTLS协议处理也全都自己实现,仅使用必要的库,这些库往往操作系统自带,如openssl、srtp。

如果需使用h.265到h.264的转码,我也避免使用极其臃肿的FFmpeg,而是直接使用短小精悍的libde265和libx264,这两个库很多操作系统也有预装。(实际上FFmpeg底层也会套娃般使用这两个库)

7、配置简单

只使用一个ini文件。

三、浏览器前端和效果

我对jssip进行了封装,呼叫摄像头就一条语句:

rtcMonitor.makeCall(摄像头id);

只有一个参数,即摄像头id,是一个符合 GB./T28181 标准的20位字符串,通常可从监控管理平台(如海康)获取。

下面是前端调用的demo说明:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title> WebRTC 获取 GB28181 摄像头音视频 </title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 20px; background-color: #f4f4f4; }
        .container { width: 90%; max-width: 800px; margin: auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        input, button { padding: 10px; margin: 5px; width: 90%; box-sizing: border-box; }
        .status { margin-top: 10px; font-weight: bold; }
        .videos { display: flex; justify-content: space-around; margin-top: 20px; }
        .videos video { width: 45%; max-width: 350px; border: 1px solid #ccc; background-color: #000; }
        #localVideo { transform: scaleX(-1); } /* 镜像本地视频 */

	.video-container {
	  width: 100%; /* 父容器宽度设为100% */
	}

	.video-container video {
	  max-width: 100%; /* 视频宽度不超过父容器的最大宽度 */
	  height: auto;   /* 高度自动,以保持原始宽高比 */
	}
    </style>
    <script src="./RtcMonitor.js" type="module"></script>
</head>
<body>
    <div class="container">
        <h1>WebRTC 获取 GB28181摄像头 音视频</h1>
        <div class="status" id="status">未连接...</div>
        <hr>
        
        <h2>SIP 账户设置</h2>
        <input type="text" id="sip_id" placeholder="5001" value="5001">
        <input type="password" id="password" placeholder="密码" value="password123">
        <button id="connectBtn">初始化并连接</button>
        
        <hr>
        
        <h2>呼叫摄像头</h2>
        <input type="text" id="target_id" placeholder="摄像头id,20位国标编号" value="34020000001320000001">
        <button id="callBtn">呼叫</button>
        <button id="hangupBtn">挂断</button>

        <div class="video-container">
            <video id="remoteVideo" autoplay playsinline></video>
        </div>
    </div>

    <script type="module">
        import RtcMonitor from './RtcMonitor.js'
        var rtcMonitor = null;

        const connectBtn = document.getElementById('connectBtn');
        const callBtn = document.getElementById('callBtn');
        const hangupBtn = document.getElementById('hangupBtn');
        // 按钮事件监听
        connectBtn.addEventListener('click', connect);
        callBtn.addEventListener('click', makeCall);
        hangupBtn.addEventListener('click', hangupCall);
        // 初始化
        callBtn.disabled = true;
        hangupBtn.disabled = true;

        // 1. 连接 SIP 服务器
        function connect() {
            const sipIdEl = document.getElementById('sip_id').value.toString();
            const passwordEl = document.getElementById('password').value.toString();
            var remoteVideoEl = document.getElementById('remoteVideo');
            var statusEl = document.getElementById("status");

            // 创建一个 RtcMonitor 类,5个参数:
            // sipId - 如果有多台或者多个窗口需显示监控画面,需不同的sipId
            // password - 对应sipId的密码。网关服务器校验
            // wss_uri - 网关服务器url,必须是合法的 wss 地址
            // remoteVideoEl - video 元素,用来显示监控流的音视频
            // statusEl - 用来显示状态的文字信息,可以为null,表示不显示状态
            rtcMonitor = new RtcMonitor(sipIdEl, passwordEl, 'wss://192.168.0.103:4536/wss', remoteVideoEl, statusEl);
            rtcMonitor.register();

            connectBtn.disabled = true;
            callBtn.disabled = false;
        }

        // 2. 发起呼叫
        function makeCall() {
            const targetIdEl = document.getElementById('target_id').value.toString();

            // 向网关服务器发起呼叫,请求摄像头的音频视频流
            // 只有一个参数,即摄像头id,是一个符合 GB./T28181 标准的 20位字符串,通常可从监控管理平台(如海康)获取
            rtcMonitor.makeCall(targetIdEl);

            hangupBtn.disabled = false;
            callBtn.disabled = true;
        }

        // 3. 挂断
        function hangupCall() {
            // 如果需要切换看其它摄像头,需调用本函数挂断当前会话,再调用 makeCall(另一个摄像头id)
            rtcMonitor.hangup();

            hangupBtn.disabled = true;
            callBtn.disabled = false;
        }
    </script>
</body>
</html>

安卓手机chrome浏览器跑上面demo的效果(使用自签名证书,所以地址栏显示感叹号):

相关推荐
xiaohua0708day5 小时前
关于解决js中MediaRecorder录制的webm视频没有进度条的问题
javascript·音视频
Niuguangshuo5 小时前
音频特征提取算法介绍
算法·音视频
长沙红胖子Qt6 小时前
FFmpeg开发笔记(十三):ffmpeg采集麦克风音频pcm重采样为aac录音为AAC文件
笔记·ffmpeg·音视频
魔猴疯猿8 小时前
轻松搭建RTMP推流、WebRTC拉流服务器SRS服务,源码编译安装
服务器·elasticsearch·webrtc
皇族崛起2 天前
【音频标注】- 音频标注项目调研
音视频·解决方案·音频标注·样本标注·项目规划
hazy1k2 天前
K230基础-录放视频
网络·人工智能·stm32·单片机·嵌入式硬件·音视频·k230
wearegogog1232 天前
基于块匹配的MATLAB视频去抖动算法
算法·matlab·音视频
aqi002 天前
FFmpeg开发笔记(八十二)使用国产直播服务器smart_rtmpd执行推流操作
ffmpeg·音视频·直播·流媒体