流媒体服务器、海康威视⼤华摄像头实现视频监控、直播解决⽅案
随着互联⽹+物联⽹进程的加快,视频监控应⽤领域变得越来越⼴泛,其中海康威视⼤华等品牌的摄像头频繁出现在视野中。由于去年也实现过智慧⼯地项⽬上的视频监控⽅案,加上当今直播趋势不减。现在总结⼀下:
缘由:是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://
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#代码):
1using Newtonsoft.Json;
2using System;
3using System.Collections.Generic;
4using System.Linq;
5using System.Net.Http;
6using System.Net.Http.Headers;
7using System.Text;
8using System.Threading.Tasks;
9using YJT.Common;
10
11/*20190819 by suzong */
12namespace YJT.Wisdom.Api.lib
13 {
14///<summary>
15///萤⽯云请求封装
16///</summary>
17public class YsClient
18 {
19private static readonly string requestUrl = "open.ys7/";
20private static readonly string appKey = "";//官⽹注册获得
21private static readonly string appSecret = "";//官⽹注册获得
22
23///<summary>
24///获得token
25///</summary>
26///<returns>{code:200,data:{accessToken:"",expireTime:精确到毫秒}}</returns>
27public static async Task<string> GetToken()
28 {
29string key = ConfigHelper.GetSetting("CacheKey:YsToken") ?? "YsAccessToken";
30string tokenStr = MemoryCacheHelper.Get(key)?.ToString();
31if (string.IsNullOrEmpty(tokenStr))
32 {
33string 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 }
39return tokenStr;
40 }
41
42///<summary>
43///添加设备
44///</summary>
45///<param name="deviceSerial">设备序列号</param>
46///<param name="validateCode">设备验证码</param>
47///<returns></returns>
48public static async Task<YsResult> SaveDevice(string deviceSerial, string validateCode)
49 {
50if (string.IsNullOrEmpty(deviceSerial) || string.IsNullOrEmpty(validateCode))
51return new YsResult() { code = "-1", msg = "缺少验证码或序列号" };
52
53string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/add?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}&validateCode={validateCode.ToUpper()}");
54return 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>
63public static async Task<YsResult> OffEncryption(string deviceSerial, string validateCode)
64 {
65if (string.IsNullOrEmpty(deviceSerial) || string.IsNullOrEmpty(validateCode))
66return new YsResult() { code = "-1", msg = "缺少验证码或序列号" };
67string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/encrypt/off?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}&validateCode={validateCode.ToUpper()}"); 68return 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>
77public static async Task<YsResult> DeleteDevice(string deviceSerial)
78 {
79if (string.IsNullOrEmpty(deviceSerial))
80return new YsResult() { code = "-1", msg = "缺少序列号" };
81
82string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/delete?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}");
83return 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>
93public static async Task<string> GetPlayWss(string deviceSerial, string validateCode)
94 {
95if (string.IsNullOrEmpty(deviceSerial) || string.IsNullOrEmpty(validateCode))
96return null;
97//{"retcode":0,"msg":"成功","data":{"tokens":["ot.cadfwa3t0dkdn62x5qf257es7dbq1cie-1vwkltfwtz-1w1jc79-9eabx2bbz"],"params":"&auth=1&biz=4&cln=100"}}
98string str = await HttpHelper.HttpGetAsync($"{requestUrl}jssdk/ezopen/getStreamToken?accessToken={GetToken().Result}&num=1&type=live");
99 YsResult result = JsonConvert.DeserializeObject<YsResult>(str);
100if (de == 0)
101 {
102string tokensStr = result?.data?.tokens[0];
103string paramStr = result?.data["params"];
104//wss://jsdecoder.ys7:20006/live?dev=设备序列号&chn=1&stream=2&ssn=刚才获取的tokens[0]+刚才获取的params的字符串。作为wssUrl,此地址可以加上checkCode=验证码作为视频加
密传输。105return $"wss://jsdecoder.ys7:20006/live?dev={deviceSerial}&chn=1&stream=2&ssn={tokensStr}{paramStr}&checkCode={validateCode}";
106 }
107return null;
108 }
109
110///<summary>
111///获取直播地址 #返回RTMP地址
112///</summary>
113///<param name="token"></param>
114///<param name="deviceSerial">设备序列号</param>
115///<returns>返回rtmp</returns>
116public static async Task<string> GetPlayRtmp(string deviceSerial)
117 {
118if (string.IsNullOrEmpty(deviceSerial))
119return null;
120string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/live/address/get?accessToken={GetToken().Result}&source={deviceSerial}:1");
121 YsResult result = JsonConvert.DeserializeObject<YsResult>(str);
122if (de.Equals("200"))
123return result?.data[0]?.rtmp;
124return 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>
143public static async Task<YsResult<YsRoles>> GetDeviceRole(string deviceSerial)
144 {
145if (string.IsNullOrEmpty(deviceSerial))
146return new YsResult<YsRoles>() { code = "-1", msg = "缺少序列号" };
147
148string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/capacity?accessToken={GetToken().Result}&deviceSerial={deviceSerial.ToUpper()}");
149return 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>
160public static async Task<YsResult> CradleControlStarts(string token, string deviceSerial, int direction, int speed)
161 {
162if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(deviceSerial))
163return null;
164string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/ptz/start?accessToken={token}&deviceSerial={deviceSerial}&channelNo=1&direction={direction}&speed={speed}"); 165return 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>
175public static async Task<YsResult> CradleControlEnd(string token, string deviceSerial, int direction)
176 {
177if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(deviceSerial))
178return null;
179string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/ptz/stop?accessToken
={token}&deviceSerial={deviceSerial}&channelNo=1&direction={direction}");
180return JsonConvert.DeserializeObject<YsResult>(str);
181 }
182
183///<summary>
184///获取单个设备信息
185///</summary>
186///<param name="token"></param>
187///<param name="deviceSerial">设备序列号</param>
188///<returns></returns>
189public static async Task<YsResult> GetDeviceInfo(string deviceSerial)
190 {
191if (string.IsNullOrEmpty(deviceSerial))
192return null;
193string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/device/info?accessToken={GetToken().Result}&deviceSerial={deviceSerial}");
194 YsResult result = JsonConvert.DeserializeObject<YsResult>(str);
195if (de.Equals("200"))
196return result;
197return null;
198 }
199
200///<summary>
201///开通直播功能
202///</summary>
203///<param name="deviceSerial">设备序列号</param>
204///<returns></returns>
205public static async Task<YsResult> LiveOpen(string deviceSerial)
206 {
207if (string.IsNullOrEmpty(deviceSerial))
208return null;
209string str = await HttpHelper.HttpPostAsync($"{requestUrl}api/lapp/live/video/open?accessToken={GetToken().Result}&source={deviceSerial}:1");
210return JsonConvert.DeserializeObject<YsResult>(str);
211 }
212
213
214 }
215
216///<summary>
217///萤⽯云返回对象
218///</summary>
219public class YsResult<T>
220 {
221public string code { get; set; }
222public T data { get; set; }
223public string msg { get; set; }
224public int retcode { get; set; }
225 }
226public class YsResult : YsResult<dynamic>
227 {
228 }
229
230///<summary>
231///萤⽯云设备能⼒集
232///</summary>
233public class YsRoles
234 {
235///<summary>
236///是否⽀持视频图像加密 0 - 不⽀持, 1 - ⽀持
237///</summary>
238public int supprot_encrypt { get; set; } = 0;
239///<summary>
240///是否⽀持修改设备加密密码: 0 - 不⽀持, 1 - ⽀持
241///</summary>
242public int support_modify_pwd { get; set; } = 0;
243///<summary>
244///是否⽀持云台上下转动 0 - 不⽀持, 1 - ⽀持
245///</summary>
246public int ptz_top_bottom { get; set; } = 0;
247///<summary>
248///是否⽀持云台左右转动 0 - 不⽀持, 1 - ⽀持
249///</summary>
250public int ptz_left_right { get; set; } = 0;
251///<summary>
252///是否⽀持云台45度⽅向转动 0 - 不⽀持, 1 - ⽀持
253///</summary>
254public int ptz_45 { get; set; } = 0;
255///<summary>
256///是否⽀持云台缩放控制 0 - 不⽀持, 1 - ⽀持
257///</summary>
258public int ptz_zoom { get; set; } = 0;
259///<summary>
260///是否⽀持焦距模式 0 - 不⽀持, 1 - ⽀持
261///</summary>
262public int ptz_focus { get; set; } = 0;
263 }
264
265 }
萤⽯云请求封装
三.使⽤开源流媒体框架
开源流媒体框架就很多了,常见的SRS国产的。安装推流拉流。可⽤于直播/录播/视频客服等多种场景,其定位是运营级的互联⽹直播服务器集。传送门:喜欢的可以⾃⼰去了解了解。
提醒:你所购买的摄像头硬件上都会有摄像头的名称序列号验证码信息,摄像头⼚商⽐如海康会有搜索局域⽹内摄像头的⼀个⼯具(官⽹去)。⼀个Web界⾯的后台⽤于设置摄像头通道配置信息等,在局域⽹内连接上摄像头浏览器地址栏输⼊对应的地址就可以登录当前摄像头后台。
附赠⼏个rtsp rtmp免费测试地址(可以先让前端⽤这些地址先实现播放功能):
1 rtsp://184.72.239.149/vod/mp4:v
2 rtsp://195.200.199.8/mpeg4/media.amp
3 rtmp://media3.sinovision:1935/live/livestream
<
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论