隨着互聯網+物聯網進程的加快,視頻監控應用領域變得愈來愈普遍,其中海康威視 大華等品牌的攝像頭頻繁出如今視野中。因爲去年也實現過智慧工地項目上的視頻監控方案,加上當今直播趨勢不減。如今總結一下:前端
原因:是1對N 點對多的直播方式, 通常都是採用服務器轉發,因此此處不考慮WebRTC這種端對端的方式,WebRTC將在下一篇文章中講解下實現思路。nginx
前提:須要海康威視或大華的攝像頭,大華攝像頭清晰度 品質較好,但相對於海康的攝像頭較貴,因此海康威視的攝像頭更受口袋歡迎。web
一.自建流媒體服務器
第一種方式就是自建流媒體服務器,而後本身實現採集推流 到服務器 拉流到客戶端播放。先看一張圖:api
- 先客戶端軟件或設備採集視頻流和語音流,或者是攝像頭硬件採集的畫面流等(如何採集就屬於硬件相關的問題了,此處不討論)
- 而後經過推流的方式推到流媒體服務器,推流協議可使用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........................緩存
- 流媒體服務器作一些編碼轉碼處理等將流分發給各個客戶端,進而進行拉流播放。那麼問題來了 如何實現流媒體服務器呢?如何架設???
- 架設上咱們可使用nginx rtmp-module模塊來架設,架設好後就可使用rtmp推流給它。還能夠用上面第2點中的ffmpeg命令寫一個bat腳原本測試攝像頭和架設的流媒體。
- PC端播放使用rtmp Flash來進行播放(H5中的Video標籤瞭解一下),移動端播放使用HLS m3u8 rtmp來進行播放(具體播放方式視項目框架狀況而定)。看網上有人還使用flv + http stream 進行播放的。
- 後期出現了併發 播放量增多的壓力能夠把nginx作分層(接入層+交換層),或者是轉發一下作負載均衡,或者CDN來支撐。前期若是考慮到後期會使用CDN也能夠直接跳過nginx 一開始用cdn的直播服務。
二.接入第三方平臺
在以前的項目中是購買了海康威視的攝像頭,因此爲了方便快捷的開發,是接入了第三方平臺,由第三方平臺進行管理和轉發。大致流程是往第三方平臺註冊攝像頭信息(序列號 驗證碼),而後流直接走第三方平臺,本身服務端只須要獲取三方平臺的API接口便能得知播放地址 直接客戶端播放。使用的是螢石雲。服務器
咱們能夠在本身的項目中往螢石雲註冊攝像頭信息(也就是調用螢石雲接口 往螢石雲寫一條數據),而後在須要用的地方獲取螢石雲API接口播放地址。徹底不用管流的處理(得充值)。網絡
提供一份對螢石雲請求封裝的類(C#代碼): 併發
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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...