热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

流媒体服务器、海康威视大华摄像头实现视频监控、直播解决方案

随着互联网+物联网进程的加快,视频监控应用领域变得越来越广泛,其中海康威视大华等品牌的摄像头频繁出现在视野中。由于去年也实现过智慧工地项目上的视频监控方案,加上当今直播趋势不减

  随着互联网+物联网进程的加快,视频监控应用领域变得越来越广泛,其中海康威视 大华等品牌的摄像头频繁出现在视野中。由于去年也实现过智慧工地项目上的视频监控方案,加上当今直播趋势不减。现在总结一下:

缘由:是1对N 点对多的直播方式, 一般都是采用服务器转发,所以此处不考虑WebRTC这种端对端的方式,WebRTC将在下一篇文章中讲解下实现思路。

前提:需要海康威视或大华的摄像头,大华摄像头清晰度 品质较好,但相对于海康的摄像头较贵,所以海康威视的摄像头更受口袋欢迎。

一.自建流媒体服务器

  第一种方式就是自建流媒体服务器,然后自己实现采集推流 到服务器 拉流到客户端播放。先看一张图:

  1. 先客户端软件或设备采集视频流和语音流,或者是摄像头硬件采集的画面流等(如何采集就属于硬件相关的问题了,此处不讨论)
  2. 然后通过推流的方式推到流媒体服务器,推流协议可以使用RTMP RMSP,这2种都是基于tcp的 不会丢包。但是很容易造成高延迟(具体的看服务器 网络 是否做CDN来支撑)。
    1 //可指定h264或h265编码,可以把h265编码看成是h264编码的升级版,在码率 体积 清晰度 移动补偿上更友好些
    2 //大体结构为:rtsp://摄像头用户名:密码@地址:端口 服务器上地址参数...
    3 rtsp://admin:yjt_jiankong@192.168.0.60:554/h264/ch1/main/av_stream
    4 rtsp://admin:yjt_jiankong@192.168.0.60:554/Streaming/Channels/101?transportmode=unicast

    以上方式只是实现了流推送到了服务器,并没有指定它播放地址以及播放的转码。因此我们可以考虑使用ffmpeg,这是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。也就是使用ffmpeg不光可以本地采集流还可以指定推送到那一台服务器上和它的播放地址等等;

    1 //ffmpeg -re -i表示使用的协议和协议的参数,具体的参数意义请百度
    2 //接着是和上面一样的推流,这里使用的是rtsp,建议用rtmp,本帅在使用中感觉rtmp兼容性更好 web前端使用rtmp更方便。比如前端用Flash插件。或者Video标签等等。
    3 //然后是基于tcp 转码 播放的地址,比如播放地址是:rtsp://117.250.250.250/Cameratest
    4 ffmpeg -re -i rtsp://admin:yjt_jiankong@192.168.0.60:554/h264/ch1/main/av_stream -rtsp_transport tcp -vcodec h264 -f rtsp rtsp://localhost/test
    5 ffmpeg -i rtsp://admin:yjt_jiankong@192.168.0.60:554/h264/ch1/main/av_stream -rtsp_transport tcp -vcodec h264 -f rtsp rtsp://117.250.250.250/Cameratest

     注意播放地址前指定播放协议,比如rtsp rtsp://117.250.250.250/Cameratest。如果是rtmp那么最后就应该是:rtmp rtmp://117........................

  3. 流媒体服务器做一些编码转码处理等将流分发给各个客户端,进而进行拉流播放。那么问题来了  如何实现流媒体服务器呢?如何架设???
  4. 架设上我们可以使用nginx rtmp-module模块来架设,架设好后就可以使用rtmp推流给它。还可以用上面第2点中的ffmpeg命令写一个bat脚本来测试摄像头和架设的流媒体。
  5. PC端播放使用rtmp Flash来进行播放(H5中的Video标签了解一下),移动端播放使用HLS m3u8 rtmp来进行播放(具体播放方式视项目框架情况而定)。看网上有人还使用flv + http stream 进行播放的。
  6. 后期出现了并发 播放量增多的压力可以把nginx做分层(接入层+交换层),或者是转发一下做负载均衡,或者CDN来支撑。前期如果考虑到后期会使用CDN也可以直接跳过nginx 一开始用cdn的直播服务。

二.接入第三方平台

  在之前的项目中是购买了海康威视的摄像头,所以为了方便快捷的开发,是接入了第三方平台,由第三方平台进行管理和转发。大体流程是往第三方平台注册摄像头信息(序列号 验证码),然后流直接走第三方平台,自己服务端只需要获取三方平台的API接口便能得知播放地址 直接客户端播放。使用的是萤石云。

 

 

 

   我们可以在自己的项目中往萤石云注册摄像头信息(也就是调用萤石云接口 往萤石云写一条数据),然后在需要用的地方获取萤石云API接口播放地址。完全不用管流的处理(得充值)。

   提供一份对萤石云请求封装的类(C#代码):  

  1 using Newtonsoft.Json;
  2 using System;
  3 using System.Collections.Generic;
  4 using System.Linq;
  5 using System.Net.Http;
  6 using System.Net.Http.Headers;
  7 using System.Text;
  8 using System.Threading.Tasks;
  9 using YJT.Common;
 10 
 11 /*20190819 by suzong */
 12 namespace YJT.Wisdom.Api.lib
 13 {
 14     /// 
 15     /// 萤石云请求封装
 16     /// 
 17     public class YsClient
 18     {
 19         private static readonly string requestUrl = "https://open.ys7.com/";
 20         private static readonly string appKey = "";//官网注册获得
 21         private static readonly string appSecret = "";//官网注册获得
 22 
 23         /// 
 24         /// 获得token
 25         /// 
 26         /// {code:200,data:{accessToken:"",expireTime:精确到毫秒}}
 27         public static async Task<string> GetToken()
 28         {
 29             string key = ConfigHelper.GetSetting("CacheKey:YsToken") ?? "YsAccessToken";
 30             string tokenStr = MemoryCacheHelper.Get(key)?.ToString();
 31             if (string.IsNullOrEmpty(tokenStr))
 32             {
 33                 string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/token/get?appKey={appKey}&appSecret={appSecret}");
 34                 YsResult result = JsonConvert.DeserializeObject(str);
 35                 //缓存token 缓存时间为5天
 36                 tokenStr = result?.data?.accessToken;
 37                 MemoryCacheHelper.Set(key, tokenStr, TimeSpan.FromDays(5));
 38             }
 39             return tokenStr;
 40         }
 41 
 42         /// 
 43         /// 添加设备
 44         /// 
 45         /// 设备序列号
 46         /// 设备验证码
 47         /// 
 48         public static async Task SaveDevice(string deviceSerial, string validateCode)
 49         {
 50             if (string.IsNullOrEmpty(deviceSerial) || string.IsNullOrEmpty(validateCode))
 51                 return new YsResult() { code = "-1", msg = "缺少验证码或序列号" };
 52 
 53             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/add?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}&validateCode={validateCode.ToUpper()}");
 54             return JsonConvert.DeserializeObject(str);
 55         }
 56 
 57         /// 
 58         /// 关闭视频加密
 59         ///        
 60         /// 设备序列号
 61         /// 设备验证码
 62         /// {code:200}
 63         public static async Task OffEncryption(string deviceSerial, string validateCode)
 64         {
 65             if (string.IsNullOrEmpty(deviceSerial) || string.IsNullOrEmpty(validateCode))
 66                 return new YsResult() { code = "-1", msg = "缺少验证码或序列号" };
 67             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/encrypt/off?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}&validateCode={validateCode.ToUpper()}");
 68             return JsonConvert.DeserializeObject(str);
 69         }
 70 
 71         /// 
 72         /// 删除设备
 73         /// 
 74         /// 
 75         /// 设备序列号
 76         /// {code:200}
 77         public static async Task DeleteDevice(string deviceSerial)
 78         {
 79             if (string.IsNullOrEmpty(deviceSerial))
 80                 return new YsResult() { code = "-1", msg = "缺少序列号" };
 81 
 82             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/delete?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}");
 83             return JsonConvert.DeserializeObject(str);
 84         }
 85 
 86         /// 
 87         /// 获取直播地址 WSS地址 #get请求 每次获取 
 88         /// 
 89         /// 
 90         /// 设备序列号
 91         /// 设备验证码
 92         /// 
 93         public static async Task<string> GetPlayWss(string deviceSerial, string validateCode)
 94         {
 95             if (string.IsNullOrEmpty(deviceSerial) || string.IsNullOrEmpty(validateCode))
 96                 return null;
 97             //{"retcode":0,"msg":"成功","data":{"tokens":["ot.cadfwa3t0dkdn62x5qf257es7dbq1cie-1vwkltfwtz-1w1jc79-9eabx2bbz"],"params":"&auth=1&biz=4&cln=100"}}
 98             string str = await HttpHelper.HttpGetAsync($"{requestUrl}jssdk/ezopen/getStreamToken?accessToken={GetToken().Result}&num=1&type=live");
 99             YsResult result = JsonConvert.DeserializeObject(str);
100             if (result.retcode == 0)
101             {
102                 string tokensStr = result?.data?.tokens[0];
103                 string paramStr = result?.data["params"];
104                 //wss://jsdecoder.ys7.com:20006/live?dev=设备序列号&chn=1&stream=2&ssn=刚才获取的tokens[0]+刚才获取的params的字符串。作为wssUrl,此地址可以加上checkCode=验证码作为视频加密传输。
105                 return $"wss://jsdecoder.ys7.com:20006/live?dev={deviceSerial}&chn=1&stream=2&ssn={tokensStr}{paramStr}&checkCode={validateCode}";
106             }
107             return null;
108         }
109 
110         /// 
111         /// 获取直播地址 #返回RTMP地址
112         /// 
113         /// 
114         /// 设备序列号
115         /// 返回rtmp
116         public static async Task<string> GetPlayRtmp(string deviceSerial)
117         {
118             if (string.IsNullOrEmpty(deviceSerial))
119                 return null;
120             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/live/address/get?accessToken={GetToken().Result}&source={deviceSerial}:1");
121             YsResult result = JsonConvert.DeserializeObject(str);
122             if (result.code.Equals("200"))
123                 return result?.data[0]?.rtmp;
124             return null;
125         }
126 
127         /// 
128         /// 获取设备可有的权限
129         /// 
130         /// 
131         /// 设备序列号
132         /// data:
133         ///{
134         ///    supprot_encrypt 是否支持视频图像加密 0 - 不支持, 1 - 支持
135         ///    support_modify_pwd 是否支持修改设备加密密码: 0 - 不支持, 1 - 支持
136         ///    ptz_top_bottom 是否支持云台上下转动 0 - 不支持, 1 - 支持
137         ///    ptz_left_right 是否支持云台左右转动 0 - 不支持, 1 - 支持
138         ///    ptz_45 是否支持云台45度方向转动 0 - 不支持, 1 - 支持
139         ///    ptz_zoom 是否支持云台缩放控制 0 - 不支持, 1 - 支持
140         ///    ptz_focus 是否支持焦距模式 0 - 不支持, 1 - 支持
141         ///}code: 200
142         /// 
143         public static async Task> GetDeviceRole(string deviceSerial)
144         {
145             if (string.IsNullOrEmpty(deviceSerial))
146                 return new YsResult() { code = "-1", msg = "缺少序列号" };
147 
148             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/capacity?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}");
149             return JsonConvert.DeserializeObject>(str);
150         }
151 
152         /// 
153         /// 云台控制开始
154         /// 
155         /// 
156         /// 设备序列号
157         /// 方向 (操作命令:0 - 上,1 - 下,2 - 左,3 - 右,4 - 左上,5 - 左下,6 - 右上,7 - 右下,8 - 放大,9 - 缩小,10 - 近焦距,11 - 远焦距)
158         /// 速度 (云台速度:0 - 慢,1 - 适中,2 - 快)
159         /// {code:200}
160         public static async Task CradleControlStarts(string token, string deviceSerial, int direction, int speed)
161         {
162             if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(deviceSerial))
163                 return null;
164             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/ptz/start?accessToken={token}&deviceSerial={deviceSerial}&channelNo=1&direction={direction}&speed={speed}");
165             return JsonConvert.DeserializeObject(str);
166         }
167 
168         /// 
169         /// 云台控制结束
170         /// 
171         /// 
172         /// 设备序列号
173         /// 方向 (操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距)
174         /// {code:200}
175         public static async Task CradleControlEnd(string token, string deviceSerial, int direction)
176         {
177             if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(deviceSerial))
178                 return null;
179             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/ptz/stop?accessToken={token}&deviceSerial={deviceSerial}&channelNo=1&direction={direction}");
180             return JsonConvert.DeserializeObject(str);
181         }
182 
183         /// 
184         /// 获取单个设备信息
185         /// 
186         /// 
187         /// 设备序列号
188         /// 
189         public static async Task GetDeviceInfo(string deviceSerial)
190         {
191             if (string.IsNullOrEmpty(deviceSerial))
192                 return null;
193             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/info?accessToken={GetToken().Result}&deviceSerial={deviceSerial}");
194             YsResult result = JsonConvert.DeserializeObject(str);
195             if (result.code.Equals("200"))
196                 return result;
197             return null;
198         }
199 
200         /// 
201         /// 开通直播功能
202         /// 
203         /// 设备序列号
204         /// 
205         public static async Task LiveOpen(string deviceSerial)
206         {
207             if (string.IsNullOrEmpty(deviceSerial))
208                 return null;
209             string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/live/video/open?accessToken={GetToken().Result}&source={deviceSerial}:1");
210             return JsonConvert.DeserializeObject(str);
211         }
212 
213 
214     }
215 
216     /// 
217     /// 萤石云返回对象
218     /// 
219     public class YsResult
220     {
221         public string code { get; set; }
222         public T data { get; set; }
223         public string msg { get; set; }
224         public int retcode { get; set; }
225     }
226     public class YsResult : YsResult<dynamic>
227     {
228     }
229 
230     /// 
231     /// 萤石云设备能力集
232     /// 
233     public class YsRoles
234     {
235         /// 
236         /// 是否支持视频图像加密 0 - 不支持, 1 - 支持
237         /// 
238         public int supprot_encrypt { get; set; } = 0;
239         /// 
240         /// 是否支持修改设备加密密码: 0 - 不支持, 1 - 支持
241         /// 
242         public int support_modify_pwd { get; set; } = 0;
243         /// 
244         /// 是否支持云台上下转动 0 - 不支持, 1 - 支持
245         /// 
246         public int ptz_top_bottom { get; set; } = 0;
247         /// 
248         /// 是否支持云台左右转动 0 - 不支持, 1 - 支持
249         /// 
250         public int ptz_left_right { get; set; } = 0;
251         /// 
252         /// 是否支持云台45度方向转动 0 - 不支持, 1 - 支持
253         /// 
254         public int ptz_45 { get; set; } = 0;
255         /// 
256         /// 是否支持云台缩放控制 0 - 不支持, 1 - 支持
257         /// 
258         public int ptz_zoom { get; set; } = 0;
259         /// 
260         /// 是否支持焦距模式 0 - 不支持, 1 - 支持
261         /// 
262         public int ptz_focus { get; set; } = 0;
263     }
264 
265 }
萤石云请求封装

 

三.使用开源流媒体框架

   开源流媒体框架就很多了,常见的SRS国产的。安装 推流 拉流。可用于直播/录播/视频客服等多种场景,其定位是运营级的互联网直播服务器集群。传送门:http://www.os-s-rs.net/srs.release/releases/ 喜欢的可以自己去了解了解。


提醒:你所购买的摄像头硬件上都会有摄像头的名称 序列号 验证码信息,摄像头厂商比如海康会有搜索局域网内摄像头的一个工具(官网去找)。一个Web界面的后台用于设置摄像头通道 配置信息等,在局域网内连接上摄像头 浏览器地址栏输入对应的地址就可以登录当前摄像头后台。

附赠几个rtsp rtmp免费测试地址(可以先让前端用这些地址先实现播放功能):

1 rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov
2 rtsp://195.200.199.8/mpeg4/media.amp
3 rtmp://media3.sinovision.net:1935/live/livestream

 End...

 


推荐阅读
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 负载均衡_Nginx反向代理动静分离负载均衡及rewrite隐藏路径详解(Nginx Apache MySQL Redis)–第二部分
    nginx反向代理、动静分离、负载均衡及rewrite隐藏路径详解 ... [详细]
  • 护墙_搭建LVS负载均衡NAT和DR模式
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了搭建LVS负载均衡NAT和DR模式相关的知识,希望对你有一定的参考价值。 ... [详细]
  • LVS-DR直接路由实现负载均衡示例
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 如何方便地退订邮件列表,避免混乱和烦恼
    本文介绍了如何方便地退订邮件列表,避免混乱和烦恼。文章指出,退订邮件列表可能会造成混乱,特别是当被意外添加到列表中时。为了快速、轻松地取消订阅,建议不要将退订电子邮件发送到用于发布消息的电子邮件地址。文章还介绍了邮件列表由邮件列表软件控制,作为邮件列表成员,可以对该软件进行一些用户控制。一些邮件列表允许使用自动电子邮件地址退订,但这可能会带来一些混乱。最后,文章提到退订邮件列表需要向电子邮件服务器发送特殊命令来脱离列表。 ... [详细]
  • SQL Server 内存中OLTP内部机制概述(一)
    内存中OLTP(项目名为“Hekaton”)是一个新的完全集成到SQLServer中的数据库引擎组件。它专为访问内存常驻数据的OLTP工作负荷而进行优化。内存中OLTP有助于OLT ... [详细]
  • 抖音服务器带宽有多大,才能供上亿人同时刷?
    最近看到一个有意思的提问:抖音服务器带宽有多大,为什么能够供那么多人同时刷?今天来给大家科普一下。 ... [详细]
  • 一面自我介绍对象相等的判断,equals方法实现。可以简单描述挫折,并说明自己如何克服,最终有哪些收获。职业规划表明自己决心,首先自己不准备继续求学了,必须招工作了。希望去哪 ... [详细]
author-avatar
我是雅小贱-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有