RTMP H5 直播流技術解析
RTMP 是什麼
RTMP 全稱即是 Real-Time Messaging Protocol
。顧名思義就是用來作為實時通訊的一種協議。該協議是 Adobe 搞出來的。主要是用來傳遞音視訊流的。它通過一種自定義的協議,來完成對指定直播流的播放和相關的操作。和現行的直播流相比,RTMP
主要的特點就是高效,這裡,我就不多費口舌了。我們先來了解一下 RTMP 是如何進行握手的。
RTMP 握手
RTMP 是基於 TCP 三次握手之後的,所以,RTMP 不是和 TCP 一個 level 的。它本身是基於 TCP 的可靠性連線。RTMP 握手的方式如圖:
(C 代表 Client,S 代表 Server)
它主要是通過兩端的欄位內容協商,來完成可信度認證的。基本過程如下:
- client: 客戶端需要發 3 個包。C0,C1,C2
- server: 服務端也需要發同樣 3 個包。 S0,S1,S2。
整個過程如上圖所述,但實際上有些細節需要注意。
握手開始:
【1】 客戶端傳送 C0,C1 包
此時,客戶端處於等待狀態。客戶端有兩個限制:
- 客戶端在未接受到 S1 之前不能傳送 C2 包
- 客戶端在未接收到 S2 之前不能傳送任何實際資料包
【2】 服務端在接受到 C0,傳送 S0,S1 包。也可以等到接受到 C1 之後再一起傳送,C1 包的等待不是必須的。
此時,服務端處於等待狀態。服務端有兩個限制:
- 服務端在未接受到 C1 之前不能傳送 S2.
- 服務端在未接收到 C2 之前不能傳送任何實際資料包
【3】客戶端接受到 S1/S0 包後,傳送 C2 包。
【4】服務端接受到 C2 包後,返回 S2 包,並且此時握手已經完成。
不過,在實際應用中,並不是嚴格按照上面的來。因為 RTMP 並不是強安全性的協議,所以,S2/C2 包只需要 C1/S1 中的內容,就可以完成內容的拼接。
這麼多限制,說白了,其實就是一種通用模式:
- C0+C1
- S0+S1+S2
- C2
接下來,我們來具體看看 C/S 012 包分別代表什麼。
C0 && S0
C0 和 S0 其實區別不大,我這裡主要講解一下 C0,就差不多了。首先,C0 的長度為 1B。它的主要工作是確定 RTMP 的版本號。
- C0:客戶端傳送其所支援的 RTMP 版本號:3~31。一般都是寫 3。
- S1:服務端返回其所支援的版本號。如果沒有客戶端的版本號,預設返回 3。
C1 && S1
C1/S1 長度為 1536B。主要目的是確保握手的唯一性。格式為:
- time: 傳送時間戳,這個其實不是很重要,不過需要記住,不要超出 4B 的範圍即可。
- zero: 保留值 0.
- random: 該欄位長尾 1528B。主要內容就是隨機值,不管你用什麼產生都可以。它主要是為了保證此次握手的唯一性,和確定握手的物件。
C2 && S2
C2/S2 的長度也是 1536B。相當於就是 S1/C1 的響應值。上圖也簡單說明了就是,對應 C1/S1 的 Copy 值,不過第二個欄位有區別。基本格式為:
- time: 時間戳,同上,也不是很重要
- time2: C1/S1 傳送的時間戳。
- random: S1/C1 傳送的隨機數。長度為 1528B。
這裡需要提及的是,RTMP 預設都是使用 Big-Endian 進行寫入和讀取,除非強調對某個欄位使用 Little-Endian 位元組序。
上面握手協議的順序也是根據其中相關的欄位來進行制定的。這樣,看起來很容易啊哈,但是,我們並不僅僅停留在瞭解,而是要真正的瞭解,接下來,我們來實現一下,如果通過 Buffer 來進行 3 次握手。這裡,我們作為 Client 端來進行請求的發起,假設 Server 端是按照標準進行傳送即可。
Buffer 實操握手
我們使用 Buffer 實操主要涉及兩塊,一個塊是 request server 的搭建,還有一塊是 Buffer 的拼接。
Request Server 搭建
這裡的 Server 是直接使用底層的 TCP 連線。
如下,一個簡易的模板:
const client = new net.Socket();
client.connect({
port: 1935,
host: },
()=>{
console.log("connected");
});
client.on('data',(data)=>{
client.write('hello');
});
不過,為了更好的進行實際演練,我們通過 EventEmitter
的方式,來做一個篩選器。這裡,我們使用 mitt 模組來做代理。
const Emitter = require('mitt')();
然後,我們只要分析的就是將要接受到的 S0/1/2 包。根據上面的位元組包圖,可以清楚的知道包裡面的詳細內容。這裡,為了簡單起見,我們排除其他協議的包頭,只是針對 RTMP 裡面的包。而且,我們針對的只有 3 種包,S0/1/2。為了達到這種目的,我們需要在 data
時間中,加上相應的鉤子才行。
這裡,我們借用 Now 直播的 RTMP 流來進行相關的 RTMP 直播講解。
Buffer 操作
Server 的搭建其實上網搜一搜,應該都可以搜尋出來。關鍵點在於,如何針對 RTMP 的實操握手進行 encode/decode。所以,這裡,我們針對上述操作,來主要講解一下。
我們主要的工作量在於如何構造出 C0/1/2。根據上面格式的描述,大家應該可以清楚的知道 C0/1/2 裡面的格式分別有啥。
比如,C1 中的 time 和 random,其實並不是必須欄位,所以,為了簡單起見,我們可以預設設為 0。具體程式碼如下:
class C {
constructor() {
this.time;
this.random;
}
C0() {
let buf = Buffer.alloc(1);
buf[0] = 3;
return buf;
}
C1() {
let buf = Buffer.alloc(1536);
return buf;
}
/**
* write C2 package
* @param {Number} time the 4B Number of time
* @param {Buffer} random 1528 byte
*/
produceC2(){
let buf = Buffer.alloc(1536);
// leave empty value as origin time
buf.writeUInt32BE(this.time, 4);
this.random.copy(buf,8,0,1528);
return buf;
}
get getC01(){
return Buffer.concat([this.C0(),this.C1()]);
}
get C2(){
return this.produceC2();
}
}
接下來,我們來看一下,結合 server 完成的 RTMP 客戶端服務。
const Client = new net.Socket();
const RTMP_C = new C();
Client.connect({
port: 1935,
host:
}, () => {
console.log('connected')
Client.write(RTMP_C.getC01);
});
Client.on('data',res=>{
if(!res){
console.warn('received empty Buffer ' + res);
return;
}
// start to decode res package
if(!RTMP_C.S0 && res.length>0){
RTMP_C.S0 = res.readUInt8(0);
res = res.slice(1);
}
if(!RTMP_C.S1 && res.length>=1536){
RTMP_C.time = res.readUInt32BE(0);
RTMP_C.random = res.slice(8,1536);
RTMP_C.S1 = true;
res = res.slice(1536);
console.log('send C2');
Client.write(RTMP_C.C2);
}
if(!RTMP_C.S2 && res.length >= 1536){
RTMP_C.S2 = true;
res = res.slice(1536);
}
})
詳細程式碼可以參考 gist。
RTMP 基本架構
RTMP 整個內容,除了握手,其實剩下的就是一些列圍繞 type id 的 message。為了讓大家更清楚的看到整個架構,這裡簡單陳列了一份框架:
在 Message 下的 3 個一級子 Item 就是我們現在將要大致講解的內容。
可以看到上面所有的 item 都有一個共同的父 Item–Message。它的基本結構為:
- Header: header 部分用來標識不同的 typeID,告訴客戶端相應的 Message 型別。另外,還有個功效就是多路分發。
- Body: Body 內容就是相應傳送的資料。這個根據不同的 typeID 來說,格式就完全不一樣了。
下面,我們先了解一下 Header 和不同 typeID 的內容:
Header
RTMP 中的 Header 分為 Basic Header 和 Message Header。需要注意,他們兩者並不是獨立的,而是相互聯絡。Message Header 的結構由 Basic Header 的內容來決定。
接下來,先分開來講解:
Basic Header
BH(基礎頭部)主要是定義了該 chunk stream ID 和 chunk type。需要注意的是,BH 是變長度的,即,它的長度範圍是 1-3B。怎麼講呢?就是根據不同的 chunk stream ID 來決定具體的長度。CS ID(Chunk Stream ID)本身的支援的範圍為 <= 65597 ,差不多為 22bit。當然,為了節省這 3B 的內容。 Adobe 搞了一個比較繞的理論,即,通過如下格式中的 CS ID 來確定:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
即,通過 2-7 bit 位來確定整個 BH 的長度。怎麼確定呢?
RTMP 規定,CS ID 的 0,1,2 為保留字,你在設定 CS ID 的時候只能從 3 開始。
- CS ID: 0 ==> 整個 BH 長為 2B,其中可以表示的 Stream ID 數量為 64-319。例如:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 0 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
注意上面的 cs id - 64。這個代表的就是,你通過切割第二個 byte 時,是將得到的值加上 64。即:2th byte + 64 = CS ID
- CS ID: 1 ==> 整個 BH 長為 3B。可以儲存的 Stream ID 數量為 64-65599。例如:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 1 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
當然,後面 CS ID 的計算方法也是最後的結果加上 64。
- CS ID >2 ==> 整個 BH 長為 1B。可以儲存的 Stream ID 數量為 3-63。例如:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
最後強調一下,因為 RTMP 規定,CS ID 的 0,1,2 為保留字,所以,0,1,2 不作為 CS ID。綜上所述,CS ID 的起始位為 3(並不代表它是 3 個 Stream)。
上面我並沒有提到 fmt 欄位,這其實是用來定義 Message Header 的。
Message Header
根據前面 BH 中 fmt 欄位的定義,可以分為 4 種 MH(Message Header)。或者說,就是一種 MH 格式會存在從繁到簡 4 種:
fmt: 0
當 fmt 為 0 時,MH 的長度為 11B。該型別的 MH 必須要流的開頭部分,這包括當進行快退或者點播時重新獲取的流。該結構的整體格式如下:
也就是說,當 fmt 為 0 時,其格式是一個完整的 MH。
- timestamp 是絕對時間戳。用來代表當前流編碼。
- message length: 3B, 傳送 message 的長度。
- type id: 1B
- stream id: 4B, 傳送 message stream id 的值。是 little-endian 寫入格式!
fmt: 1
當 fmt 為 1 時,MH 的長度為 7B。該型別的 MH 不帶 msg stream id。msg stream id 由前面一個 package 決定。該數值主要由前一個 fmt 為 0 的 MH 決定。該型別的 MH 通常放在 fmt 為 0 之後。
fmt: 2
當 fmt 為 2 時,MH 的長度為 3B。該型別的 MH 只包括一個 timestamp delta 欄位。其它的資訊都是依照前面一個其他型別 MH 決定的。
fmt: 3
當 fmt 為 3時,這其實 RTMP 裡面就沒有了 MH。官方定義,該型別主要全部都是 payload 的 chunk,其 Header 資訊和第一個非 type:3
的頭一致。因為這主要用於 chunk 中,這些 chunk 都是從一個包裡面切割出來的,所以除了第一個
chunk 外,其它的 chunk 都可以採用這種格式。當 fmt 為 3時,計算它的 timestamp 需要注意幾點,如果前面一個 chunk 裡面存在 timestrameDelta
,那麼計算 fmt 為 3 的 chunk 時,就直接相加,如果沒有,則是使用前一個
chunk 的 timestamp
來進行相加,用程式碼表示為:
prevChunk.timeStamp += prevChunk.timeStampDelta || prevChunk.timeStamp;
不過,當 fmt: 3 的情況一般很難遇到。因為,他要求前面幾個包必須存在 fmt 為 0/1/2 的情況。
接下來的就是 Message Body 部分。
Message Body
上面說的主要是 Message Header 的公用部分,但是,對於具體的 RTMP Message 來說,裡面的 type 會針對不同的業務場景有不同的格式。Message 全部內容如上圖所示:
這裡,我們根據流程圖的一級子 item 來展開講解。
PCM
PCM 全稱為:Protocol Control Messages(協議控制訊息)。主要使用來溝通 RTMP 初始狀態的相關連線資訊,比如,windows size,chunk size 等。
PCM 中一共有 5 種不同的 Message 型別,是根據 Header 中的 type ID 決定的,範圍是 1~6 (不包括 4)。另外,PCM 在構造的時候需要注意,它 Heaer 中的 message stream id 和 chunk stream id 需要設定為固定值:
- message stream ID 為 0
- chunk stream ID 為 2
如圖所示:
OK,我們接下來一個一個來介紹一下:
Set Chunk Size(1)
看名字大家應該都能猜到這類資訊是用來幹啥的。該型別的 PCM 就是用來設定 server 和 client 之間正式傳輸資訊的 chunk 的大小,type ID 為 1。那這有啥用呢?
SCS(Set Chunk Size) 是針對正式傳送資料而進行資料大小的傳送限制。一般預設為 128B。不過,如果 server 覺得太小了,想傳送更大的包給你,比如 132B,那麼 server 就需要給你傳送一個 SCS,告知你,接下來“我傳送給你的資料大小是 132B”。
- 0: 只能設為 0 ,用來表示當前的 PCM 的型別。
- chunk size: 用來表示後面傳送正式資料時的大小。範圍為 1-16777215。
如下,提供過 wireshark 抓包的結果:
Abort Message(2)
該類 PCM 是用來告訴 client,丟棄指定的 stream 中,已經載入到一半或者還未載入完成的 Chunk Message。它需要指定一個 chunk stream ID。
基本格式為:
- chunk stream id: 指定丟棄 chunk message 的 stream
Acknowledgement(3)
該協議資訊其實就是一個 ACK 包,在實際使用是並沒有用到,它主要是用來作為一個 ACK 包,來表示兩次 ACK 間,接收端所能接收的最大位元組數。
它基本格式為:
- sequence number[4B]: 大小為 4B
不過,該包在實際應用中,沒有多高的出現頻率。
Window Acknowledgement Size(5)
這是用來協商傳送包的大小的。這個和上面的 chunk size
不同,這裡主要針對的是客戶端可接受的最大資料包的值,而 chunk size 是指每次傳送的包的大小。也可以叫做 window
size
。一般電腦設定的大小都是 500000B。
詳細格式為:
通過,wireshark 抓包的結果為:
Set Peer Bandwidth(6)
這是 PCM 中,最後一個包。他做的工作主要是根據網速來改變傳送包的大小。它的格式和 WAS 類似,不過後面帶上了一個 Type
用來標明當前頻寬限制演算法。當一方接收到該資訊後,如果設定的 window size 和前面的 WAS 不一致,需要返回一個
WAS 來進行顯示改變。
基本格式為:
其中 Limit Type 有 3 個取值:
- 0: Hard,表示當前頻寬需要和當前設定的 window size 匹配
- 1: Soft,將當前寬頻設定為該資訊定義的 window size,或者已經生效的 window size。主要取決於誰的 window size 更小
- 2: Dynamic,如果前一個 Limit Type 為 Hard 那麼,繼續使用
Hard
為基準,否則忽略該次協議資訊。
實際抓包情況可以參考:
UCM
全稱為:User Control Message
(使用者控制資訊)。它的 Type ID 只能為 4。它主要是傳送一些對視訊的控制資訊。其傳送的條件也有一定的限制:
- msg stream ID 為 0
- chunk stream ID 為 2
它的 Body 部分的基本格式為:
UCM 根據 Event Type 的不同,對流進行不同的設定。它的 Event Type 一共有 6 種格式 Stream Begin(0)
,Stream
EOF(1)
,StreamDry(2)
,SetBuffer
Length(3)
,StreamIs Recorded(4)
,PingRequest(6)
,PingResponse(7)
。
相關推薦
RTMP H5 直播流技術解析
RTMP 是什麼 RTMP 全稱即是 Real-Time Messaging Protocol。顧名思義就是用來作為實時通訊的一種協議。該協議是 Adobe 搞出來的。主要是用來傳遞音視訊流的。它通過一種自定義的協議,來完成對指定直播流的播放和相關的操作。和現行的直播
Ubuntu下搭建Nginx伺服器+整合RTMP視訊直播流處理(邊做邊做更新)
簡介: 要求:做一個網頁獲取攝像頭的視訊流,然後將視訊流經過流伺服器推送到後臺視訊處理伺服器,再由後臺視訊處理伺服器推送到流伺服器,最終推送到頁面。如圖: 經過查閱資料,目前有red5以及nginx+nginx-rtmp-module實現,選擇第二種方式來實現。 準備工作:
視訊監控安防平臺-國標GB28181轉RTSP和RTMP推流進行H5(RTMP/HLS)直播(支援GB28181-2016版本、支援公網碼流傳輸)
視訊監控安防平臺-國標GB28181轉RTSP、RTMP和HLS管理平臺(支援GB28181-2016版本、支援公網碼流傳輸) 最近抽了點時間把國標GB28181轉RTSP、RTMP和HLS管理平臺做了簡單的整理,把相應的Demo也整理好了,
obs nginx-rtmp-module搭建流媒體服務器實現直播 ding
video 接下來 監聽 comm 地址 什麽 ip地址 automake text 接下來我就簡單跟大家介紹一下利用nginx來搭建流媒體服務器。 我選擇的是騰訊雲服務器 1、下載nginx-rtmp-module: nginx-rtmp-module的官方gith
極速搭建RTMP直播流伺服器+webapp (vue) 簡單實現直播效果
在嘗試使用webRTC實現webapp直播失敗後,轉移思路開始另外尋找可行的解決方案。在網頁上嘗試使用webRTC實現視訊的直播與看直播,在谷歌瀏覽器以及safari瀏覽器上測試是可行的。但是基於基座打包為webapp後不行,所以直播的話建議還是原生的好。HBuilder自帶的H5+有提供了原生
實現直接輸出h264直播流的rtmp伺服器
RTMP(Real Time Messaging Protocol)是常見的流媒體協議,用來傳輸音視訊資料,結合flash,廣泛用於直播、點播、聊天等應用,以及pc、移動、嵌入式等平臺,是做流媒體開發經常會接觸到的協議。我之前曾經寫過一篇文章“RTMP協議傳送H.264編碼及AAC編碼的音視
基於SRS搭建RTMP直播流媒體伺服器
軟體定位 SRS 定位是運營級的網際網路直播伺服器叢集,追求更好的概念完整性和最簡單實現的程式碼。 運營級:商業運營追求極高的穩定性、良好的系統對接、錯誤排查和處理機制。譬如日誌檔案格式、reload、系統 HTTP 介面、提供 init.d 指令碼、轉發、轉碼和邊緣回多源站
利用nginx與nginx-rtmp-module搭建流媒體伺服器實現直播
轉自:https://www.cnblogs.com/suiyuewuxin/p/7256972.html 使用環境是centos 7.0+nginx;可以實現簡單的流媒體服務。 先下載nginx-rtmp-module拓展: nginx-rtmp-module的官方github地址:h
Windows10環境下 Nginx+ffmpeg自搭伺服器製作RTMP直播流
Windows10環境下 Nginx+ffmpeg自搭伺服器製作RTMP直播流 學習筆記 所需條件: nginx-rtmp-module(帶rtmp模組) ,連結:https://link.jianshu.com/?t=http%3A%2F%2Fnginx-win.ec
直播大火,HTML5直播技術你知道嗎?H5直播方案分析講解
2017年視訊直播可謂是大火,各種視訊直播平臺陸續登場,H5直播也逐漸成熟,今天就來講解H5視訊直播可行性方案的分析。 目前 WEB 上主流的視訊直播方案有 HLS 和 RTMP,移動 WEB 端目前以 HLS 為主(HLS存在延遲性問題,也可以藉助 video.
一對一直播原始碼如何選擇直播流媒體技術(CDN)
快上西樓,怕天放、浮雲遮月。但喚取、玉纖橫笛,一聲吹裂。 誰做冰壺浮世界,最憐玉斧修時節。問常娥、孤冷有愁無。應華髮。 雲液滿,瓊杯滑。長袖起,清歌咽。嘆十常八九,欲磨還缺。 若得長圓如此夜,人情未必看承別。把從前、離恨總成歡,歸時說 ——中秋快樂,簡短寒暄後
一對一分析直播協議和推流技術
近年來直播已成為網際網路行業的大熱話題,直播答題、遊戲直播、競賽直播等層出不窮,直播早已成為人們耳熟能詳的技術。事實上直播的興起不僅與新時代人們要求為自己代言的心理有關,同時也得益於頻寬的提速和CDN技術的發展。 伴隨著CDN技術的成熟,企業自己部署雲伺服器做直播也越來越簡單 。 本文作
Android中直播視訊技術探究之---採集攝像頭Camera視訊源資料進行推流(採用金山雲SDK)
一、前言在之前已經詳細介紹了Android中的一種視訊資料來源:Camera,不瞭解的同學可以點選進入:Android中Camera使用詳解 ,在這篇文章中我們介紹瞭如何採集攝像頭的每一幀資料,然後進行
利用nginx的nginx-rtmp-module搭建流媒體直播伺服器
Nginx除了做web伺服器之外在流媒體方面的支援也是有對應的模組,nginx-rtmp-module就是nginx的一個擴充套件模組,支援rtmp視訊推流,同時利用nginx作為web伺服器的有時可以很方便的實現直播拉流,專案官方地址是https://github.com/arut/nginx-r
通過nginx,nginx-rtmp-module實現流媒體直播
1、 下載nginx http://nginx.org/en/download.html 下載nginx-rtmp-module: nginx-rtmp-module的官方github地址:https://github.com/arut/nginx-rtmp-module
利用Nginx搭建RTMP視訊直播,點播伺服器,ffmpeg推流,回看
#下面的server是在http的一級配置標籤下的#上面的註釋對懂nginx的人是廢話,但是如果你不熟悉nginx,建議認真看看http{...# 這裡有一些其他的配置 server { listen 80;#埠 server_name rtmp-server;#設定http
各種RTMP直播流播放許可權_音視訊_資料花屏_問題檢測與分析工具EasyRTMPClient
之前的一篇部落格《網路攝像機IPCamera RTSP直播播放網路/許可權/音視訊資料/花屏問題檢測與分析助手EasyRTSPClient》,我們介紹了RTSP流的檢測和分析工具EasyRTSPClient,可以說已經是深入了我的平時運維工作中了,當我們發現有任
搭建直播伺服器,使用nginx與nginx-rtmp-module搭建流媒體伺服器;
現在,一起學習一下如何自己搭建一個流媒體伺服器吧! 本次搭建流媒體使用的環境是centos 7.0+nginx; 讓我們一起開始奇妙的流媒體之旅吧! 1、下載nginx-rtmp-module: 使用命令: git clone https://gi
直播答題技術方案解析
直播答題已經是風口,毋容置疑。對攻城獅們來說,2018 年春節是個坎,直播答題技術做細緻做到位了
搭建rtmp直播流服務之3:java開發ffmpeg實現rtsp轉rtmp並實現ffmpeg命令的介面化管理架構設計及程式碼實現
這一篇將進一步深挖java對ffmepg命令的控制並最終實現服務介面化 通知:由於很多同學反映本章程式碼的命令封裝設計的不是很好,所以對本章程式碼重新進行了實現,新版本推翻了本章原有程式碼內部實現,介面設計更加利於注入自己的實現,並增加可執行原生ffmpeg命令功