作者:Elias Zhang 聲網資深工程師,擁有從Iaas層的基礎資訊儲存服務到paas層的雲服務的職業經歷,喜歡python語言,習慣使用C#,熟悉基於和結合CDN的業務產品架構,點播、直播、雲導播等。喜歡探索問題和研究創新,擁有5項國家發明專利。
在直播是最常見的實時音視訊場景,而 RTMP 是該場景下最重要的協議之一,是很多初步接觸實時音視訊的開發者需要了解的。本文會一邊利用 winshark工具進行抓包,一邊從中分析 RTMP 協議的基本原理,幫助大家更容易地理解它。
先給出RTMP協議的原檔案 www.adobe.com/devnet/rtmp… 需要用到的時候可以參考一下~。
做推流直播接觸最多的並且最主要是RTMP協議
- RTMP協議是應用層協議,是要靠底層可靠的傳輸層(TCP)
- 協議(通常是TCP)來保證資訊傳輸的可靠性的。在基於傳輸層協議的連結建立完成後,RTMP協議也要客戶端和伺服器通過“握手”來建立基於傳輸層連結之上的RTMP Connection連結. 播放一個RTMP協議的流媒體需要經過以下幾個步驟:握手,建立網路連線,建立網路流,播放。伺服器和客戶端之間只能建立一個網路連線,但是基於該連線可以建立很多網路流。他們的關係如圖所示:
- RTMP協議傳輸時會對資料做自己的格式化,這種格式的訊息我們稱之為RTMP Message,而實際傳輸的時候為了更好地實現多路複用、分包和資訊的公平性,傳送端會把Message劃分為帶有Message ID的Chunk,每個Chunk可能是一個單獨的Message,也可能是Message的一部分,在接受端會根據chunk中包含的data的長度,message id和message的長度把chunk還原成完整的Message,從而實現資訊的收發。 協議本身的詳細欄位和流程就不在這裡詳細解釋了,主要結合看包直觀的瞭解下這個協議的流程,更詳細的內容可以查閱前面給出的官方文件。
RTMP步驟:
1. 握手
要建立一個有效的RTMP Connection連結,首先要“握手”:客戶端要向伺服器傳送C0,C1,C2(按序)三個chunk,伺服器向客戶端傳送S0,S1,S2(按序)三個chunk,然後才能進行有效的資訊傳輸。RTMP協議本身並沒有規定這6個Message的具體傳輸順序,但RTMP協議的實現者需要保證這幾點:
- 客戶端要等收到S1之後才能傳送C2
- 客戶端要等收到S2之後才能傳送其他資訊(控制資訊和真實音視訊等資料)
- 服務端要等到收到C0之後傳送S1
- 服務端必須等到收到C1之後才能傳送S2
- 服務端必須等到收到C2之後才能傳送其他資訊(控制資訊和真實音視訊等資料) 握手開始於客戶端傳送C0、C1塊。伺服器收到C0或C1後傳送S0和S1。 當客戶端收齊S0和S1後,開始傳送C2。當伺服器收齊C0和C1後,開始傳送S2。 當客戶端和伺服器分別收到S2和C2後,握手完成。
實際上真實發包如下:
我們可以看見TCP的三次握手,RTMP基於TCP的可靠傳輸。
接下去過濾rtmpt協議,rtmp的握手過程如下,我們發現真實發包是C0+C1一起發;S0,S1,S2一起發。
2. 建立網路連線(NetConnection)
a) 客戶端傳送命令訊息中的“連線”(connect)到伺服器,請求與一個服務應用例項建立連線。
開啟connect這個包,一個OSI5層協議模型,最後一個是RTMP協議傳送了connect連結訊息,檢視內容包含推流地址名,但是可以觀察到還沒有發流名,地址是有app名。
觀察一下RTMP的包頭
StreamID是每個訊息的唯一標識,劃分成Chunk和還原Chunk為Message的時候都是根據這個ID來辨識是否是同一個訊息的Chunk的,這裡面為0說明這個訊息是初始的0訊息。
Chunk stream ID:message會拆分成多個chunk,同一個Chunk Stream ID必然屬於同一個Message。
message type id(訊息的型別id):表示實際傳送的資料的型別,如8代表音訊資料、9代表視訊資料。
Format:指的是chunk type。共有4種不同的格式,其中第一種格式欄位為0,可以表示其他三種表示的所有資料,但由於其他三種格式是基於對之前chunk的差量化的表示,因此可以更簡潔地表示相同的資料,實際使用的時候還是應該採用儘量少的位元組表示相同意義的資料。因為type0是表示不同資料,其他是差量,所以可以想象如果搜不到type0的包說明這個流肯定有問題。可以通過“rtmpt.header.format == 0”過濾。
b) 伺服器接收到連線命令訊息後,傳送確認視窗大小(Window Acknowledgement Size)協議訊息到客戶端,同時連線到連線命令中提到的應用程式。
c) 伺服器傳送設定頻寬協議訊息到客戶端。
d) 客戶端處理設定頻寬協議訊息後,傳送確認視窗大小(Window Acknowledgement Size)協議訊息到伺服器端。
e) 伺服器傳送使用者控制訊息中的“流開始”(Stream Begin)訊息到客戶端。
f) 伺服器傳送命令訊息中的“結果”(_result),通知客戶端連線的狀態。 b~f如圖:在_result我們可以看到連結已經建立成功
接下去的包我們看到發了releaseStream命令,裡面的agora就是流名,所以一個推流地址我們可以抓包connect和releaseStream裡面拼接得出。
- 建立一個網路流(NetStream)
提示:網路流代表了傳送多媒體資料的通道。伺服器和客戶端之間只能建立一個網路連線,且多個網路流可以複用這一個網路連線。
a. 客戶端向伺服器傳送請求建立流(createStream)。
b. 伺服器收到請求後向客戶端傳送_result(),對建立流的訊息進行響應。此時NetStream建立完成。
4. PLAY 播放
a) 客戶端傳送命令訊息中的“播放”(play)命令到伺服器。
b) 接收到播放命令後,伺服器傳送設定塊大小(ChunkSize)協議訊息。
c) 伺服器傳送使用者控制訊息中的“streambegin”,告知客戶端流ID。
d) 播放命令成功的話,伺服器傳送命令訊息中的“響應狀態” NetStream.Play.Start & NetStream.Play.reset,告知客戶端“播放”命令執行成功。
e) 在此之後伺服器傳送客戶端要播放的音訊和視訊資料。
可以注意到裡面音訊type是8,視訊是9。
5. PUBLISH 推流
推流從握手開始和前面步驟123一致。和第四步play區別在於netstream的命令改為publish
關於本文,如果你在跟隨步驟操作或閱讀時有任何疑問,請點選這裡跳轉至原文與作者直接交流。