一、ONVIF 規范和常見視頻流傳輸協議
① ONVIF 規范
隨著視頻監控產業鏈的成熟,市面上陸陸續續出現了各式各樣的網絡攝像設備,這些設備都需要通訊協議才能進行數據傳輸。早期廠商都采用私有協議,但是現在廠商分工明確,有的負責生產制造攝像頭,有的負責開發視頻服務器,有的負責方案集成并銷售,私有協議存在嚴重的兼容問題。類似于 HTTP 協議對瀏覽器和服務器之間通信的規范,網絡攝像頭的接口也出現了標準化,其中 ONVIF 支持廠商的數目、廠商的知名度和市場占有率遙遙領先。 ONVIF 規范描述了網絡視頻的模型、接口、數據類型以及數據交互的模式,并復用了一些現有的標準,如 WS 系列標準等,目標是實現一個網絡視頻框架協議,使不同廠商所生產的網絡視頻產品(包括攝錄前端、錄像設備等)完全互通。ONVIF 規范中設備管理和控制部分所定義的接口均以 Web Services 的形式提供。ONVIF 規范涵蓋了完全的 XML 及 WSDL 的定義。每一個支持 ONVIF 規范的終端設備均須提供與功能相應的 Web Service。服務端與客戶端的簡單數據交互采用 SOAP 協議,音視頻流則通過 RTP / RTSP 進行。
② 視頻流傳輸協議
http-flv / ws-flv HTTP 協議中有個 Content-Length 字段,代表 HTTP 的 body 部分的長度。服務器回復 HTTP 請求的時候如果有這個字段,客戶端就接收這個長度的數據然后就認為數據傳輸完成了,如果服務器回復 HTTP 請求中沒有這個字段,客戶端就一直接收數據,直到服務器跟客戶端的 socket 連接斷開。要流式傳輸 flv 文件,服務器是不可能預先知道內容大小的,這時就可以使用 Transfer-Encoding: chunked 模式來連續分塊傳輸數據。 http-flv 能夠較好的穿透防火墻,它是基于 HTTP / 80 傳輸,有效避免被防火墻攔截。除此之外,它可以通過 HTTP 302 跳轉靈活調度/負載均衡,同時支持使用 HTTPS 加密傳輸。ws-flv 和 http-flv 類似,區別就是 http-flv 基于 HTTP ,只能單向傳輸數據,而 ws-flv 基于 WS 可以雙向傳輸數據。 RTMP RTMP 是 Real Time Messaging Protocol(實時消息傳輸協議)的首字母縮寫。由 Macromedia 開發的一套視頻直播協議,現在屬于 Adobe。因為 RTMP 是通過互聯網在 Flash 播放器與一個服務器之間傳輸流媒體音頻、視頻和數據而開發的一個專有協議,早期的網頁想要直播需要 Flash 播放器,所以 RTMP 協議是最好的選擇,互聯網公司也在此協議上有了不少的技術積累,國內的 CDN 對 RTMP 做過優化。 隨著 HTML5 的普及和 Flash 的停止更新,理論上會被其他協議取代,但是由于類似 flv.js 庫的出現,使得在 HTML5 網頁同樣可以播放 flv 格式的視頻,所以 RTMP 依然是當前視頻直播的主流協議。 HLS HLS 是 HTTP Live Streaming 的首字母縮寫,是由蘋果公司提出基于HTTP 的流媒體網絡傳輸協議,可實現流媒體的直播和點播,主要應用在 iOS 系統,為 iOS 設備(如 iPhone、iPad)提供音視頻直播和點播方案。HLS 協議在服務器端將直播數據流存儲為連續的、很短時長的媒體文件( MPEG-TS 格式),而客戶端則不斷的下載并播放這些小文件, 因為服務器端總是會將最新的直播數據生成新的小文件,這樣客戶端只要不停的按順序播放從服務器獲取到的文件,就實現了直播。 iOS 和 Android 都天然支持這種協議,配置簡單,直接使用 video 標簽即可。這是一種以點播的方式實現的直播,且分段文件的時長很短,因為客戶端可以比較快速的切換碼率,用于適應不同的寬帶條件。但是由于這種技術特性,導致它的延遲會高于普通的流媒體直播協議。磁盤存儲和處理 HLS 協議產生的大量小文件,會影響服務端的響應速度,且損耗磁盤的正常壽命。 RTSP RTSP(Real Time Streaming Protocol)是由 Real Network 和 Netscape 共同提出的如何有效地在 IP 網絡上傳輸流媒體數據的應用層協議。和基于 HTTP 協議的流媒體網絡傳輸協議不同,RTSP 是雙向通信的,客戶端和服務端都可以主動發起請求。 RTSP 對流媒體提供了諸如暫停,快進等控制,而它本身并不傳輸數據,RTSP 的作用相當于流媒體服務器的遠程控制。服務器端可以自行選擇使用 TCP 或 UDP 來傳送串流內容,它的語法和運作跟 HTTP 1.1 類似,但并不特別強調時間同步,所以比較能容忍網絡延遲。而且允許同時多個串流需求控制(Multicast),除了可以降低服務器端的網絡用量,還可以支持多方視頻會議(Video Conference)。
DASH DASH(Dynamic Adaptive Streaming over HTTP)全稱為“基于 HTTP 的動態自適應流”,是一種自適應比特率流技術,使高質量流媒體可以通過傳統的 HTTP 網絡服務器以互聯網傳遞。 類似蘋果公司的 HTTP Live Streaming(HLS)方案,DASH 會將內容分解成一系列小型的基于 HTTP 的文件片段,每個片段包含很短長度的可播放內容,而內容總長度可能長達數小時(例如電影或體育賽事直播)。內容將被制成多種比特率的備選片段,以提供多種比特率的版本供選用。當內容被客戶端回放時,客戶端將根據當前網絡條件自動選擇下載和播放哪一個備選方案。客戶端將選擇可及時下載的最高比特率片段進行播放,從而避免播放卡頓或重新緩沖事件。也因如此,客戶端可以無縫適應不斷變化的網絡條件并提供高質量的播放體驗,擁有更少的卡頓與重新緩沖發生率。
二、EdgerOS 對流媒體的支持
在日常生活場景中,如刷臉的門禁、無人的車庫和物品識別等,都會用到視頻流,EdgerOS 也能開發者提供了 MediaDecoder,WebMedia 兩個模塊,使得在開發的過程中可以簡單方便的將視頻流推送給前端。MediaDecoder 負責將設備推過來的實時音視頻流解碼并轉為指定格式(視頻寬高、幀率、像素)的目標視頻。 同時使用 WebMedia 可以創建一個流媒體的服務,這個服務支持雙傳輸通道,stream 通道支持 http-flv 和 ws-flv 協議,用于傳輸視頻流;data 通道支持 WS 協議,這樣客戶端和服務端直接可以相互發送簡單的控制指令。客戶端通過 stream 通道接收視頻流,使用 flv.js 等開源庫在網頁上實現實時直播的功能。
EdgerOS 通過幾個模塊用于可以處理 MediaDecoder 解碼生成的目標視頻流,以適應于不同的場景,如人臉識別,大致流程如下:
三、推流和拉流
推流:將直播的內容推送至服務器的過程,其實就是將現場的視頻信號傳到網絡的過程。“推流”對網絡要求比較高,如果網絡不穩定,直播效果就會很差,觀眾觀看直播時就會發生卡頓等現象,觀看體驗很是糟糕。要想用于推流還必須把音視頻數據使用傳輸協議進行封裝,變成流數據。常用的流傳輸協議有 RTSP、RTMP、HLS 等,使用 RTMP 傳輸的延時通常在 1–3 秒,對于手機直播這種實時性要求非常高的場景,RTMP 也成為手機直播中最常用的流傳輸協議。最后通過一定的 QoS 算法將音視頻流數據推送到網絡端,通過 CDN 進行分發。 拉流:指服務器已有直播內容,用指定地址進行拉取的過程。即是指服務器里面有流媒體視頻文件,這些視頻文件根據不同的網絡協議類型(如 RTMP、RTSP、HTTP 等)被讀取的過程,稱之為拉流,日常觀看視頻和直播就是一個拉流的過程。
四、如何應用愛智的視頻流模塊完成拉流?
① 引入模塊
在拉取視頻流的過程中,需要用到 WebMedia 模塊和 MediaDecoder 模塊,同時需要建立一個 WebApp 來與前端進行通訊并將其綁定在 WebMedia 上:
var WebApp = require ( 'webapp ') ;
var MediaDecoder = require ( 'mediadecoder ') ;
var WebMedia = require ( 'webmedia ') ;
② 選擇流媒體協議并綁定
WebMedia 支持 http-flv 和 ws-flv 兩種協議來傳輸視頻流,可以根據自己的實際需求選擇流媒體協議。將流媒體協議寫入配置并在 WebMedia 啟動時綁定到 WebMedia 上。 http-flv:
var opts = { mode: 1 , path: '/ live. flv', mediaSource: { source: 'flv ', } , streamChannel: { protocol: 'xhr ', } ,
}
var server = WebMedia . createServer ( opts, app) ;
var opts = { mode: 1 , path: '/ live. flv', mediaSource: { source: 'flv ', } , streamChannel: { protocol: 'ws ', server: wsSer} ,
}
var server = WebMedia . createServer ( opts, app) ;
③ 在移動端查看拉流URL
當 WebMedia 服務器啟動成功后,需要啟動媒體解碼模塊 MediaDecoder 并綁定拉流的 URL,可以在手機推流應用的設置中查看 URL:
④ 創建視頻流解碼模塊
使用 MediaDecoder 的接口來配置拉取視頻流并解碼的配置,配置完成后監聽 remux 事件和 header 事件,將監聽到的數據推入向前端發送視頻流的通道中,完成視頻流的拉取,解析和推送過程:
server. on ( 'start ', ( ) => { var netcam = new MediaDecoder ( ) . open ( 'rtsp : / / 192.168 . 128.102 : 8554 / live. sdp', { proto: 'tcp '} , 5000 ) ; netcam. destVideoFormat ( { width: 640 , height: 360 , fps: 1 , pixelFormat: MediaDecoder . PIX_FMT_RGB24 , noDrop: false , disable: false } ) ; netcam. destAudioFormat ( { disable: false } ) ; netcam. remuxFormat ( { enable: true , enableAudio: true , audioFormat: 'aac ', format: 'flv '} ) ; netcam. on ( 'remux ', ( frame) => { var buf = Buffer . from ( frame. arrayBuffer) ; server. pushStream ( buf) ; } ) ; netcam. on ( 'header ', ( frame) => { var buf = Buffer . from ( frame. arrayBuffer) ; server. pushStream ( buf) ; } ) ; netcam. start ( ) ;
} ) ;
⑤ MediaDecoder 的配置接口詳解
mediadecoder.destVideoFormat(fmt) 設置媒體解碼器的目標視頻格式: return {Boolean} 成功則返回 true,否則返回 false。 目標視頻格式對象包含以下成員: pixelFormat {Integer} 視頻像素格式 mediadecoder.destAudioFormat(fmt) 設置媒體解碼器的目標音頻格式: return {Boolean} 成功則返回 true,否則返回 false 目標音頻格式對象包含以下成員: channelLayout{String} 音頻通道布局 sampleRate {Integer} 音頻采樣率 sampleFormat {Integer} 音頻幀格式 mediadecoder.remuxFormat(fmt) 設置媒體解碼器的 remux 格式。remux 數據主要用于直播服務器和視頻錄制: return {Boolean} 成功則返回 true,否則返回 false 目標音頻格式對象包含以下成員: enable{Boolean} 是否啟用 remux enableAudio{Boolean} 是否啟用音頻 audioFormat {String} 音頻壓縮格式 format {String} 目標媒體格式,默認為 flv 格式。 需要注意的一點是,當需要拉取視頻的同時還要拉取音頻信息,那么就要將 MediaDecoder.remuxFormat(),接口中的 audioFormat 參數設置為 AAC(‘aac’),目前只支持這種格式的壓縮。
⑥ 測試視頻流傳輸
編寫簡單的前端測試視頻流是否推送成功,前端使用到 NodePlayer 工具處理顯示 flv 文件。 NodePlayer 是一款能夠播放 http-flv/websocket 協議直播流的工具,其特點是能夠在 PC \ Android \ iOS 的瀏覽器 Webview 內實現毫秒級低延遲直播播放。并能夠軟解碼 H.264 / H.265+AAC 流,WebGL 視頻渲染,WebAudio 音頻播放。支持在微信公眾號、朋友圈分享中打開。 NodePlayer 下載地址:https://www.nodemedia.cn/doc/web/#/1?page_id=1 前端測試代碼:
< html>
< div> < canvas id= "video1" style= "width:640px;height:480px;background-color: black;" > < / canvas>
< / div>
< body>
< script type = "text/javascript" src= "./NodePlayer.min.js" > < / script>
< script>
var player; NodePlayer . load ( ( ) => { player = new NodePlayer ( ) ; } ) ;
player. setView ( "video1" ) ; player. on ( "start" , ( ) => {
} ) ;
player. on ( "stop" , ( ) => {
} ) ;
player. on ( "error" , ( e) => {
} ) ;
player. on ( "videoInfo" , ( w, h) => { console. log ( "player on video info width=" + w + " height=" + h) ;
} )
player. on ( "audioInfo" , ( r, c) => { console. log ( "player on audio info samplerate=" + r + " channels=" + c) ;
} )
player. on ( "stats" , ( stats) => {
console. log ( "player on stats=" , stats) ;
} ) player. setVolume ( 1000 )
player. start ( "http://192.168.128.1:10002/live.flv" ) ;
< / script>
< / body>
< / html>