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

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

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

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

一.自建流媒体服务器

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

  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     /// <summary>
 15     /// 萤石云请求封装  16     /// </summary>
 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         /// <summary>
 24         /// 得到token  25         /// </summary>
 26         /// <returns>{code:200,data:{accessToken:"",expireTime:精确到毫秒}}</returns>
 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<YsResult>(str);  35                 //缓存token 缓存时间为5天
 36                 tokenStr = result?.data?.accessToken;  37                 MemoryCacheHelper.Set(key, tokenStr, TimeSpan.FromDays(5));  38  }  39             return tokenStr;  40  }  41 
 42         /// <summary>
 43         /// 添加设备  44         /// </summary>
 45         /// <param name="deviceSerial">设备序列号</param>
 46         /// <param name="validateCode">设备验证码</param>
 47         /// <returns></returns>
 48         public static async Task<YsResult> 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<YsResult>(str);  55  }  56 
 57         /// <summary>
 58         /// 关闭视频加密  59         /// </summary>       
 60         /// <param name="deviceSerial">设备序列号</param>
 61         /// <param name="validateCode">设备验证码</param>
 62         /// <returns>{code:200}</returns>
 63         public static async Task<YsResult> 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<YsResult>(str);  69  }  70 
 71         /// <summary>
 72         /// 删除设备  73         /// </summary>
 74         /// <param name="token"></param>
 75         /// <param name="deviceSerial">设备序列号</param>
 76         /// <returns>{code:200}</returns>
 77         public static async Task<YsResult> 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<YsResult>(str);  84  }  85 
 86         /// <summary>
 87         /// 获取直播地址 WSS地址 #get请求 每次获取  88         /// </summary>
 89         /// <param name="token"></param>
 90         /// <param name="deviceSerial">设备序列号</param>
 91         /// <param name="validateCode">设备验证码</param>
 92         /// <returns></returns>
 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<YsResult>(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         /// <summary>
111         /// 获取直播地址 #返回RTMP地址 112         /// </summary>
113         /// <param name="token"></param>
114         /// <param name="deviceSerial">设备序列号</param>
115         /// <returns>返回rtmp</returns>
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<YsResult>(str); 122             if (result.code.Equals("200")) 123                 return result?.data[0]?.rtmp; 124             return null; 125  } 126 
127         /// <summary>
128         /// 获取设备可有的权限 129         /// </summary>
130         /// <param name="token"></param>
131         /// <param name="deviceSerial">设备序列号</param>
132         /// <returns>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         /// </returns>
143         public static async Task<YsResult<YsRoles>> GetDeviceRole(string deviceSerial) 144  { 145             if (string.IsNullOrEmpty(deviceSerial)) 146                 return new YsResult<YsRoles>() { 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<YsResult<YsRoles>>(str); 150  } 151 
152         /// <summary>
153         /// 云台控制开始 154         /// </summary>
155         /// <param name="token"></param>
156         /// <param name="deviceSerial">设备序列号</param>
157         /// <param name="direction">方向 (操做命令:0 - 上,1 - 下,2 - 左,3 - 右,4 - 左上,5 - 左下,6 - 右上,7 - 右下,8 - 放大,9 - 缩小,10 - 近焦距,11 - 远焦距)</param>
158         /// <param name="speed">速度 (云台速度:0 - 慢,1 - 适中,2 - 快)</param>
159         /// <returns>{code:200}</returns>
160         public static async Task<YsResult> 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<YsResult>(str); 166  } 167 
168         /// <summary>
169         /// 云台控制结束 170         /// </summary>
171         /// <param name="token"></param>
172         /// <param name="deviceSerial">设备序列号</param>
173         /// <param name="direction">方向 (操做命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距)</param>
174         /// <returns>{code:200}</returns>
175         public static async Task<YsResult> 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<YsResult>(str); 181  } 182 
183         /// <summary>
184         /// 获取单个设备信息 185         /// </summary>
186         /// <param name="token"></param>
187         /// <param name="deviceSerial">设备序列号</param>
188         /// <returns></returns>
189         public static async Task<YsResult> 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<YsResult>(str); 195             if (result.code.Equals("200")) 196                 return result; 197             return null; 198  } 199 
200         /// <summary>
201         /// 开通直播功能 202         /// </summary>
203         /// <param name="deviceSerial">设备序列号</param>
204         /// <returns></returns>
205         public static async Task<YsResult> 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<YsResult>(str); 211  } 212 
213 
214  } 215 
216     /// <summary>
217     /// 萤石云返回对象 218     /// </summary>
219     public class YsResult<T>
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     /// <summary>
231     /// 萤石云设备能力集 232     /// </summary>
233     public class YsRoles 234  { 235         /// <summary>
236         /// 是否支持视频图像加密 0 - 不支持, 1 - 支持 237         /// </summary>
238         public int supprot_encrypt { get; set; } = 0; 239         /// <summary>
240         /// 是否支持修改设备加密密码: 0 - 不支持, 1 - 支持 241         /// </summary>
242         public int support_modify_pwd { get; set; } = 0; 243         /// <summary>
244         /// 是否支持云台上下转动 0 - 不支持, 1 - 支持 245         /// </summary>
246         public int ptz_top_bottom { get; set; } = 0; 247         /// <summary>
248         /// 是否支持云台左右转动 0 - 不支持, 1 - 支持 249         /// </summary>
250         public int ptz_left_right { get; set; } = 0; 251         /// <summary>
252         /// 是否支持云台45度方向转动 0 - 不支持, 1 - 支持 253         /// </summary>
254         public int ptz_45 { get; set; } = 0; 255         /// <summary>
256         /// 是否支持云台缩放控制 0 - 不支持, 1 - 支持 257         /// </summary>
258         public int ptz_zoom { get; set; } = 0; 259         /// <summary>
260         /// 是否支持焦距模式 0 - 不支持, 1 - 支持 261         /// </summary>
262         public int ptz_focus { get; set; } = 0; 263  } 264 
265 }
萤石云请求封装

 

三.使用开源流媒体框架

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


提醒:你所购买的摄像头硬件上都会有摄像头的名称 序列号 验证码信息,摄像头厂商好比海康会有搜索局域网内摄像头的一个工具(官网去找)。一个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...