大华海康摄像头人家自己是怎么在web上播放视频的

您所在的位置:网站首页 如何自己做摄像头监控视频 大华海康摄像头人家自己是怎么在web上播放视频的

大华海康摄像头人家自己是怎么在web上播放视频的

2024-07-14 00:20| 来源: 网络整理| 查看: 265

最近处理安防视频,怎么把摄像头视频在web上展示费了很大功夫,当然这一篇不是讲解我是怎么显示的,而是回答当时领导问我的一个问题,人家大华自己是怎么显示的? 我们知道大华海康大部分摄像头只对外提供rtsp流地址,但是rtsp在web上无法播放,只能转协议http-flv rtmp websocket-flv,但是看大华设置网页,为什么人家自己可以播放出来呢? 其实F12看看前端加载的js脚本大概就清楚了,采用了asm.js技巧+websocket 在这里插入图片描述

在这里插入图片描述如上,打开一个大华摄像头web设置页面进行抓包吧。 ps:关于websocket可以看一下这一篇博文: https://www.cnblogs.com/songwenjie/p/8575579.html 在这里插入图片描述

最开始的三个是tcp握手阶段, 然后http请求websocket建立 c-> s 也就是播放rtspj的h5播放器其实是一个rtsp客户端

在这里插入图片描述

c->s GET /rtspoverwebsocket HTTP/1.1 Host: 192.168.1.108 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: http://192.168.1.108 Sec-WebSocket-Version: 13 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Cookie: username=admin; secure; DWebClientSessionID=5cd89136afd1986f52066a9906e6aad7 Sec-WebSocket-Key: I3HWohtJyIUq1k1ggpXiiQ== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

在这里插入图片描述

HTTP/1.1 101 Switching Protocols Upgrade: WebSocket Connection: Upgrade Access-Control-Allow-Origin: * Sec-WebSocket-Accept: ymSnlzdYM+OgjlYQmNyqHkqZoQc=

上面两步就是websocket握手了,然后浏览器播放器向服务器发送包装过的rtsp请求,首先我们回顾一下rtsp握手步骤: https://www.cnblogs.com/linhaostudy/p/11140823.html 在这里插入图片描述首先是OPTIONS阶段,客户端询问服务器又那些方法可以用:

在这里插入图片描述

OPTIONS rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3 RTSP/1.0 CSeq: 1

在这里插入图片描述

RTSP/1.0 401 Unauthorized CSeq: 1 WWW-Authenticate: Digest realm="Login to 8ff1ccdacb1a8ca6522b07116f1b979d", nonce="ec9596f109c69ee241d35cb0b59722ae"

(上一次好像没有授权什么的,又重新来了一次)

在这里插入图片描述

OPTIONS rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3 RTSP/1.0 CSeq: 2 Authorization: Digest username="admin", realm="Login to 8ff1ccdacb1a8ca6522b07116f1b979d", nonce="ec9596f109c69ee241d35cb0b59722ae", uri="rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3", response="c8b8cf6769ef9860c520e61dc6eaa4df"

在这里插入图片描述

RTSP/1.0 200 OK CSeq: 2 Server: Rtsp Server/3.0 Public: OPTIONS, DESCRIBE, ANNOUNCE, SETUP, PLAY, RECORD, PAUSE, TEARDOWN, SET_PARAMETER, GET_PARAMETER

DESCRIBE阶段:客户端请求服务发送流媒体sdp信息 在这里插入图片描述

DESCRIBE rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3 RTSP/1.0 CSeq: 3 Authorization: Digest username="admin", realm="Login to 8ff1ccdacb1a8ca6522b07116f1b979d", nonce="ec9596f109c69ee241d35cb0b59722ae", uri="rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3", response="c8b8cf6769ef9860c520e61dc6eaa4df"

在这里插入图片描述

RTSP/1.0 200 OK CSeq: 3 x-Accept-Dynamic-Rate: 1 Content-Base: rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3/ Cache-Control: must-revalidate Content-Length: 525 Content-Type: application/sdp v=0 o=- 2252371056 2252371056 IN IP4 0.0.0.0 s=Media Server c=IN IP4 0.0.0.0 t=0 0 a=control:* a=packetization-supported:DH a=rtppayload-supported:DH a=range:npt=now- m=video 0 RTP/AVP 98 a=control:trackID=0 a=framerate:25.000000 a=rtpmap:98 H265/90000 a=fmtp:98 profile-id=1;sprop-sps=QgEBAUAAAAMAAAMAAAMAAAMAmaACgIAuHxOWu5Gwa5VB;sprop-pps=RAHAc8BMkA==;sprop-vps=QAEMAf//AUAAAAMAAAMAAAMAAAMAmawJ a=recvonly m=application 0 RTP/AVP 100 a=control:trackID=3 a=rtpmap:100 stream-assist-frame/90000 a=recvonly

SETUP阶段,设置会话属性以及传输模式 在这里插入图片描述

SETUP rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3/trackID=0 RTSP/1.0 CSeq: 4 Authorization: Digest username="admin", realm="Login to 8ff1ccdacb1a8ca6522b07116f1b979d", nonce="ec9596f109c69ee241d35cb0b59722ae", uri="rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3", response="c8b8cf6769ef9860c520e61dc6eaa4df" Transport: DH/AVP/TCP;unicast;interleaved=0-1 Session: null

在这里插入图片描述

RTSP/1.0 200 OK CSeq: 4 Session: 432883521468;timeout=60 Transport: DH/AVP/TCP;unicast;interleaved=0-1;ssrc=00000000 x-Dynamic-Rate: 1

(又重新请求了一次SETUP) 在这里插入图片描述

SETUP rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3/trackID=3 RTSP/1.0 CSeq: 5 Authorization: Digest username="admin", realm="Login to 8ff1ccdacb1a8ca6522b07116f1b979d", nonce="ec9596f109c69ee241d35cb0b59722ae", uri="rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3", response="c8b8cf6769ef9860c520e61dc6eaa4df" Transport: DH/AVP/TCP;unicast;interleaved=6-7 Session: 432883521468

在这里插入图片描述

RTSP/1.0 200 OK CSeq: 5 Session: 432883521468;timeout=60 Transport: DH/AVP/TCP;unicast;interleaved=6-7;ssrc=00000000 x-Dynamic-Rate: 1

PLAY阶段: 在这里插入图片描述

PLAY rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3 RTSP/1.0 CSeq: 6 Session: 432883521468 Authorization: Digest username="admin", realm="Login to 8ff1ccdacb1a8ca6522b07116f1b979d", nonce="ec9596f109c69ee241d35cb0b59722ae", uri="rtsp://192.168.1.108:80/cam/realmonitor?channel=1&subtype=0&proto=Private3", response="c8b8cf6769ef9860c520e61dc6eaa4df"

在这里插入图片描述

RTSP/1.0 200 OK CSeq: 6 Session: 432883521468 RTP-Info: url=trackID=0;seq=0;rtptime=0,url=trackID=3;seq=0;rtptime=0

上面是rtsp握手,结束后就开始穿视频数据了。 还是没有明白,但是大致有个思路了,就是rtsp通过websocket代理服务器握手,很明显就是websocket肯定有一个tcp客户端跟rtsp服务器交互,websocket起到一个透传作用。 在这里插入图片描述大致流程就是上述,其实解决的核心问题是H5播放器,这个播放器要支持rtsp协议。 一种做法是将接受过来的h264 aac直接封装为MP4播放,另外一种是通过wasm技术自己实现解码。

H5 + websocket_rtsp_proxy 实现视频流直播 https://github.com/Streamedian/html5_rtsp_player Streamedian 提供了一种“html5_rtsp_player + websock_rtsp_proxy”的技术方案,可以通过html5的video标签直接播放RTSP的视频流。 整个架构如下图所示,分为服务器端和浏览器端两部分: 在这里插入图片描述 在这里插入图片描述其实F12可以发现前端是一个h5播放器,并且采用了wasm解码技术 在这里插入图片描述 在这里插入图片描述我们再将刚才抓包转为rtsp协议: 也就是说,通过websocket代理服务器握手后,就发送数据,浏览器就可以解码播放了。

这里我专门抓一次rtsp包做个对比: 在这里插入图片描述很明显,rtsp握手后就是rtp

ps : rtsp通过tcp或者tcp发送区别 https://www.cnblogs.com/lidabo/p/4483497.html 这一章也介绍了rtp over udp https://blog.csdn.net/weixin_42462202/article/details/98986535 这篇文章作者用c#实现了一个 https://blog.csdn.net/xhydongda/article/details/81208192 IPC网络摄像机rtsp视频流web上H5播放方法 https://blog.csdn.net/weixin_42538493/article/details/106113325?utm_medium=distribute.pc_relevant_download.none-task-blog-baidujs-2.nonecase&depth_1-utm_source=distribute.pc_relevant_download.none-task-blog-baidujs-2.nonecase

这里有一个github开源的 https://github.com/Garefield/RTSP-WS-Proxy web端将要打开的rtsp地址发送给服务端,服务端打开rtsp流成功后将流的mime发送给web并开始推送fmp4数据,web利用mime初始化mse,成功后将websocket收到的二进制数据交给mse播放,程序目前只支持h264视频和aac音频,如要接入其它格式,请修改服务端,在服务端进行转码工作

下面两块是别人的c#代码,可以用来看看大致流程

其中,这个是websocket代理,主要处理websocket接受转发给rtsp,以及rtsp队列出队转发给websocket。 using Arim.Utils.Network; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Arim.Evms.Web { public class CameraWebSocketHandler : WebSocketHandler { ConcurrentDictionary ws_rtsps; public CameraWebSocketHandler(WebSocketConnectionManager webSocketConnectionManager) : base(webSocketConnectionManager) { ws_rtsps = new ConcurrentDictionary(); } public override void OnConnected(WebSocket socket) { base.OnConnected(socket); } public override async Task OnDisconnected(WebSocket socket) { var socketId = WebSocketConnectionManager.GetId(socket); if (ws_rtsps.ContainsKey(socketId)) { WSRtspContext wsrtsp; ws_rtsps.TryRemove(socketId, out wsrtsp); wsrtsp.StopReceive(); if (wsrtsp.Rtsp != null) { wsrtsp.Rtsp.Close(); } }//关闭rtsp. await base.OnDisconnected(socket); } //主要看这里,这是是从播放器接受到rtsp命令后通过这里透传 public override async Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, byte[] buffer) { if (buffer == null || buffer.Length == 0) return; var socketId = WebSocketConnectionManager.GetId(socket); WSRtspContext wsrtsp = null ; if (ws_rtsps.ContainsKey(socketId)) { wsrtsp = ws_rtsps[socketId]; } if (result.MessageType == WebSocketMessageType.Text) { string package = Encoding.UTF8.GetString(buffer); string command = getWSPCommand(package); string seq = getByKey(package, "seq"); if(command == "INIT")//建立新链接. { //我们默认rtp通过tcp传输,那么我们只需要一个tcp端口就可以了,这个端口来传输rtsp、rtp和rtcp string host = getByKey(package, "host"); string port = getByKey(package, "port"); if (port == null) port = "554";//rtsp默认端口 if (host != null) { try { RtspProxy rtsp = new RtspProxy(); //去连接rtsp bool connected = rtsp.Connect(host, Convert.ToInt32(port)); if (connected) { rtsp.Start();//开启rtsp端口接受数据 wsrtsp = new WSRtspContext(socket, rtsp, false); wsrtsp.ControlWebSocketId = socketId; ws_rtsps.TryAdd(socketId, wsrtsp); wsrtsp.Seq = seq; //返回握手. WSRtspResponse response = new WSRtspResponse(); response.Seq = seq; response.Shakehand = true; response.Channel = socketId; await socket.SendAsync(response.ToArray(), WebSocketMessageType.Text, true, CancellationToken.None); //启动接受rtsp控制报文,发送给ws. await wsrtsp.StartReceive(); } else { await removeSocket(wsrtsp); return; } } catch(Exception ex) { //Logger.Error(String.Format("connect to rtsp {0} Error:{1}", package, ex.Message)); return; } } } else if (command == "JOIN")//建立数据通道. { string channel = getByKey(package, "channel"); if (channel != null && ws_rtsps.ContainsKey(channel)) { WSRtspContext controlwsrtsp = ws_rtsps[channel]; controlwsrtsp.DataWebSocketId = socketId; WSRtspContext datawsrtsp = new WSRtspContext(socket, controlwsrtsp.Rtsp, true); datawsrtsp.ControlWebSocketId = controlwsrtsp.ControlWebSocketId; datawsrtsp.DataWebSocketId = socketId; ws_rtsps.TryAdd(socketId, datawsrtsp); //返回握手. WSRtspResponse response = new WSRtspResponse(); response.Seq = seq; await socket.SendAsync(response.ToArray(), WebSocketMessageType.Text, true, CancellationToken.None); //启动接受rtsp数据报文,发送给ws. await datawsrtsp.StartReceive(); } return; }//WSP/1.1 JOIN channel: 127.0.0.1 - 2 18467 seq: 3 else { wsrtsp.Seq = seq; try { await wsrtsp.Send(getRtspBuffer(package)); } catch { await removeSocket(wsrtsp); } } } else if(wsrtsp != null) { try { await wsrtsp.Send(buffer); } catch { await removeSocket(wsrtsp); } }//WebSocketMessageType.Data } private async Task removeSocket(WSRtspContext wsrtsp) { if (!String.IsNullOrEmpty(wsrtsp.ControlWebSocketId)) await this.WebSocketConnectionManager.RemoveSocket(wsrtsp.ControlWebSocketId); if (!String.IsNullOrEmpty(wsrtsp.DataWebSocketId)) await this.WebSocketConnectionManager.RemoveSocket(wsrtsp.DataWebSocketId); } //获取 WSP/1.1 WRAP 中的 WRAP. private static string getWSPCommand(string source) { string proto = "WSP/1.1"; int protostart = source.IndexOf(proto); int protoend = source.IndexOf("\r\n", protostart); return source.Substring(proto.Length, protoend - proto.Length).Trim(); } private static string getByKey(string source, string key) { int keyIndex = source.IndexOf(key); if (keyIndex > -1) { int indexKeyEnd = source.IndexOf("\r\n", keyIndex); if (indexKeyEnd > keyIndex) { return source.Substring(keyIndex + key.Length + 1, indexKeyEnd - keyIndex - key.Length - 1).Trim(); } } return null; } private static byte[] getRtspBuffer(string source) { if (source == null) return null; int wsmsgend = source.IndexOf("\r\n\r\n"); if (wsmsgend > -1) { int rtsplen = source.Length - wsmsgend - 4; if(rtsplen>0) { string rtspmsg = source.Substring(wsmsgend + 4, rtsplen); return ASCIIEncoding.UTF8.GetBytes(rtspmsg); } } return null; } } public class WSRtspResponse { public WSRtspResponse() { Shakehand = false; } const string Proto = "WSP/1.1 200 OK"; public string Channel { get; set; } public string Seq { get; set; } public bool Shakehand { get; set; } public byte[] RtspBuffer { get; set; } public byte[] ToArray() { StringBuilder sb = new StringBuilder(); sb.Append(Proto).Append("\r\n"); sb.Append("seq: ").Append(Seq).Append("\r\n"); if (Shakehand) sb.Append("channel: ").Append(Channel).Append("\r\n"); sb.Append("\r\n"); byte[] wsheader = ASCIIEncoding.UTF8.GetBytes(sb.ToString()); if (!Shakehand && RtspBuffer != null) { int wsheaderlength = wsheader.Length; if (RtspBuffer != null && RtspBuffer.Length > 0) { int rtsplength = RtspBuffer.Length; byte[] result = new byte[wsheaderlength + rtsplength]; Array.Copy(wsheader, result, wsheaderlength); Array.Copy(RtspBuffer, 0, result, wsheaderlength, rtsplength); return result; } else return wsheader; } else return wsheader; } } public class WSRtspContext { WebSocket _ws; RtspProxy _rtsp; bool _dataChannel; bool quitFlag = false; public WSRtspContext(WebSocket ws, RtspProxy rtsp, bool dataChannel) { _ws = ws; _rtsp = rtsp; _dataChannel = dataChannel; } public string ControlWebSocketId { get; set; } public string DataWebSocketId { get; set; } public RtspProxy Rtsp { get { return _rtsp; } } public string Seq { get;set; } public async Task Send(byte[] buffer) { await _rtsp.Send(buffer); } public async Task StartReceive() { quitFlag = false; while (!quitFlag) { List datas; bool succeed; if (_dataChannel) succeed = _rtsp.TryDequeData(out datas, 10); else succeed = _rtsp.TryDequeControl(out datas, 1); if (succeed) { foreach(byte[] data in datas) { if (!_dataChannel) { WSRtspResponse response = new WSRtspResponse(); response.Seq = Seq; response.RtspBuffer = data; //将rtsp接受到的数据透传给rtsp客户端 h5播放器 await _ws.SendAsync(response.ToArray(), _dataChannel ? WebSocketMessageType.Binary : WebSocketMessageType.Text, true, CancellationToken.None); } else { await _ws.SendAsync(data, _dataChannel ? WebSocketMessageType.Binary : WebSocketMessageType.Text, true, CancellationToken.None); } } } else { if (_dataChannel) await Task.Delay(1); else await Task.Delay(10); } } } public void StopReceive() { quitFlag = true; } } } using System; using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Arim.Utils.Network { /// /// RTSP连接代理,不解析RTSP协议和报文内容,只做透明转发. /// public class RtspProxy { public RtspProxy() { } private string hostname; private int port; private TcpClient tcpclient; private Thread _thread; private Stream _stream; bool quitflag = false; public bool Connect(string host, int port) { this.hostname = host; this.port = port; try { tcpclient = new TcpClient(host, port); } catch { return false; } if (!tcpclient.Connected) { return false; } _stream = tcpclient.GetStream(); return true; } public void Start() { quitflag = false; if (_thread == null) { _thread = new Thread(Dowork); _thread.Start(); } } public void Close() { quitflag = true; if (_thread != null) { _thread.Join(); _thread = null; } Thread.Sleep(100); try { if (_stream != null) _stream.Close(); tcpclient.Close(); } catch (Exception ex) { Logger.Write(ex); } Logger.Debug("Connection Close"); } Queue dataQueue = new Queue(); public bool TryDequeData(out List datas, int max) { datas = null; lock (dataQueue) { int count = dataQueue.Count; int toread = count > max ? max : count; if (toread > 0) { datas = new List(); for (int i = 0; i max ? max : count; if (toread > 0) { datas = new List(); for (int i=0;i bufferlength等于有剩余的内容没读完. { break; }//need readmore. var bs = new byte[end-pos]; Array.Copy(buffer, pos, bs, 0, end - pos); lock(controlQueue) { controlQueue.Enqueue(bs); } pos = end; }//control } remainlen = bufferlength - pos; for (int j = 0; j 0) { await _stream.WriteAsync(data, 0, data.Length); await _stream.FlushAsync(); } } } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3