EasyRTMP实现的rtmp推流的基本协议流程
EasyRTMP介紹
EasyRTMP是結合了多種音視頻緩存及網絡技術的一個rtmp直播推流端,包括:圓形緩沖區(circular buffer)、智能丟幀、自動重連、rtmp協議等等多種技術,能夠非常有效地適應各種平臺(Windows、Linux、ARM、Android、iOS),各種網絡環境(有線、wifi、4G),以及各種情況下的直播恢復(服務器重啟、網絡重啟、硬件設備重啟),今天我們講解的是EasyRTMP中rtmp推送連接的建立、推送H264+AAC音視頻、以及rtmp推送重連過程的詳細講解;
librtmp實現的RTMP基本協議
EasyRTMP中RTMP推送流程依然采用的是業界良心的librtmp,其穩定性和可靠性毋庸置疑,已經得到了廣大的開發者的驗證,所以EasyRTMP也直接采用了librtmp,librtmp的下載可以到:http://rtmpdump.mplayerhq.hu/?下載版本,直入主題,我們對librtmp的代碼進行分析,librtmp的推送流程主要包括:握手、分塊、Connect、Metadata、SendStream(Video&Audio)、Close等等流程,我們引用了博客http://blog.csdn.net/leixiaohua1020/article/details/42105049中對RTMP推送流程的解析:
整個程序包含3個接口函數:?
RTMP264_Connect():建立RTMP連接。?
RTMP264_Send():發送數據。?
RTMP264_Close():關閉RTMP連接。?
按照順序調用上述3個接口函數就可以完成H.264碼流的發送。
結構圖中關鍵函數的作用如下所列。
RTMP264_Connect()中包含以下函數:?
InitSockets():初始化Socket?
RTMP_Alloc():為結構體“RTMP”分配內存。?
RTMP_Init():初始化結構體“RTMP”中的成員變量。?
RTMP_SetupURL():設置輸入的RTMP連接的URL。?
RTMP_EnableWrite():發布流的時候必須要使用。如果不使用則代表接收流。?
RTMP_Connect():建立RTMP連接,創建一個RTMP協議規范中的NetConnection。?
RTMP_ConnectStream():創建一個RTMP協議規范中的NetStream。
RTMP264_Send()中包含以下函數:?
ReadFirstNaluFromBuf():從內存中讀取出第一個NAL單元?
ReadOneNaluFromBuf():從內存中讀取出一個NAL單元?
h264_decode_sps():解碼SPS,獲取視頻的寬,高,幀率信息?
SendH264Packet():發送一個NAL單元
SendH264Packet()中包含以下函數:?
SendVideoSpsPps():如果是關鍵幀,則在發送該幀之前先發送SPS和PPS?
SendPacket():組裝一個RTMPPacket,調用RTMP_SendPacket()發送出去?
RTMP_SendPacket():發送一個RTMP數據RTMPPacket
RTMP264_Close()中包含以下函數:?
RTMP_Close():關閉RTMP連接?
RTMP_Free():釋放結構體“RTMP”?
CleanupSockets():關閉Socket
RTMP直播推流中需要注意的點
這里在簡書上有一篇:http://www.jianshu.com/p/00aceabce944?已經說的很到位了,我們直接引用,希望能夠給大家帶來幫助:
1. RTMP 握手
RTMP 握手分為簡單握手和復雜握手,現在Adobe公司使用RTMP協議的產品應該用的都是復雜握手,這里不介紹,只說簡單握手。 按照網上的說法RTMP握手的過程如下
在實際工程應用中,一般是客戶端先將C0, C1塊同時發出,服務器在收到C1 之后同時將S0, S1, S2發給客戶端。S2的內容就是收到的C1塊的內容。之后客戶端收到S1塊,并原樣返回給服務器,簡單握手完成。按照RTMP協議個要求,客戶端需要校驗C1塊的內容和S2塊的內容是否相同,相同的話才徹底完成握手過程,實際編寫程序用一般都不去做校驗。
RTMP握手的這個過程就是完成了兩件事:1. 校驗客戶端和服務器端RTMP協議版本號,2. 是發了一堆數據,猜想應該是測試一下網絡狀況,看看有沒有傳錯或者不能傳的情況。RTMP握手是整個RTMP協議中最容易實現的一步,接下來才是大頭。
2. RTMP 分塊
創建RTMP連接算是比較難的地方,開始涉及消息分塊(chunking)和 AFM(也是Adobe家的東西)格式數據的一些東西,在上面提到的文章中也有介紹為什要進行RTMP分塊。
Chunk Size RTMP是按照chunk size進行分塊,chunk size指的是 chunk的payload部分的大小,不包括chunk basic header 和 chunk message header,即chunk的body的大小。客戶端和服務器端各自維護了兩個chunk size, 分別是自身分塊的chunk size 和 對端 的chunk size, 默認的這兩個chunk size都是128字節。通過向對端發送set chunk size 消息告知對方更改了 chunk size的大小,即告訴對端:我接下來要以xxx個字節拆分RTMP消息,你在接收到消息的時候就按照新的chunk size 來組包。 在實際寫代碼的時候一般會把chunk size設置的很大,有的會設置為4096,FFMPEG推流的時候設置的是 60*1000,這樣設置的好處是避免了頻繁的拆包組包,占用過多的CPU。設置太大的話也不好,一個很大的包如果發錯了,或者丟失了,播放端就會出現長時間的花屏或者黑屏等現象。Chunk Type RTMP 分成的Chunk有4中類型,可以通過 chunk basic header的 高兩位指定,一般在拆包的時候會把一個RTMP消息拆成以 Type_0 類型開始的chunk,之后的包拆成 Type_3 類型的chunk,我查看了有不少代碼也是這樣實現的,這樣也是最簡單的實現。 RTMP 中關于Message 分chunk只舉了兩個例子,這兩個例子不是很具有代表性。假如第二個message和第一個message的message stream ID 相同,并且第二個message的長度也大于了chunk size,那么該如何拆包?當時查了很多資料,都沒有介紹。后來看了一些源碼,發現第二個message可以拆成Type_1類型一個chunk, message剩余的部分拆成Type_3類型的chunk。FFMPEG中好像就是這么做的。3. RTMP 消息
-
Connect消息?
transactionID += 1 // 0x01let command:RTMPCommandMessage = RTMPCommandMessage(commandName: "connect", transactionId: transactionID, messageStreamId: 0x00)let objects:Amf0Object = Amf0Object()objects.setProperties("app", value: rtmpSocket.appName)objects.setProperties("flashVer",value: "FMLE/3.0 (compatible; FMSc/1.0)")objects.setProperties("swfUrl", value:"")objects.setProperties("tcUrl", value: "rtmp://" + rtmpSocket.hostname + "/" + rtmpSocket.appName)objects.setProperties("fpad", value: false)objects.setProperties("capabilities", value:239)objects.setProperties("audioCodecs", value:3575)objects.setProperties("videoCodecs", value:252)objects.setProperties("videoFunction",value: 1)objects.setProperties("pageUrl",value: "")objects.setProperties("objectEncoding",value: 0)
握手之后先發送一個connect 命令消息,命令里面包含什么東西,協議中沒有說,真實通信中要指定一些編解碼的信息,這些信息是以AMF格式發送的, 下面是用swift 寫的connect命令包含的參數信息:
服務器返回的是一個_result命令類型消息,這個消息的payload length一般不會大于128字節,但是在最新的nginx-rtmp中返回的消息長度會大于128字節,所以一定要做好收包,組包的工作。?
關于消息的transactionID是用來標識command類型的消息的,服務器返回的_result消息可以通過 transactionID來區分是對哪個命令的回應,connect 命令發完之后還要發送其他命令消息,要保證他們的transactionID不相同。?
發送完connect命令之后一般會發一個 set chunk size消息來設置chunk size 的大小,也可以不發。?
Window Acknowledgement Size 是設置接收端消息窗口大小,一般是2500000字節,即告訴客戶端你在收到我設置的窗口大小的這么多數據之后給我返回一個ACK消息,告訴我你收到了這么多消息。在實際做推流的時候推流端要接收很少的服務器數據,遠遠到達不了窗口大小,所以基本不用考慮這點。而對于服務器返回的ACK消息一般也不做處理,我們默認服務器都已經收到了這么多消息。?
之后要等待服務器對于connect的回應的,一般是把服務器返回的chunk都讀完組成完整的RTMP消息,沒有錯誤就可以進行下一步了。
- Create Stream 消息
創建完RTMP連接之后就可以創建RTMP流,客戶端要想服務器發送一個releaseStream命令消息,之后是FCPublish命令消息,在之后是createStream命令消息。當發送完createStream消息之后,解析服務器返回的消息會得到一個stream ID, 這個ID也就是以后和服務器通信的 message stream ID, 一般返回的是1,不固定。
- Publish Stream
推流準備工作的最后一步是 Publish Stream,即向服務器發一個publish命令,這個命令的message stream ID 就是上面 create stream 之后服務器返回的stream ID,發完這個命令一般不用等待服務器返回的回應,直接下一步發送音視頻數據。有些rtmp庫 還會發setMetaData消息,這個消息可以發也可以不發,里面包含了一些音視頻編碼的信息。
4. 發布音視頻
當以上工作都完成的時候,就可以發送音視頻了。音視頻RTMP消息的Payload中都放的是按照FLV-TAG格式封的音視頻包,具體可以參照FLV協議文檔。
5. 關于RTMP的時間戳
RTMP的時間戳在發送音視頻之前都為零,開始發送音視頻消息的時候只要保證時間戳是單增的基本就可以正常播放音視頻。我讀Srs-librtmp的源碼,發現他們是用h264的dts作為時間戳的。我在用java寫的時候是先獲取了下當前系統時間,然后每次發送消息的時候都與這個起始時間相減,得到時間戳。
6. 關于Chunk Stream ID
RTMP 的Chunk Steam ID是用來區分某一個chunk是屬于哪一個message的 ,0和1是保留的。每次在發送一個不同類型的RTMP消息時都要有不用的chunk stream ID, 如上一個Message 是command類型的,之后要發送視頻類型的消息,視頻消息的chunk stream ID 要保證和上面 command類型的消息不同。每一種消息類型的起始chunk 的類型必須是 Type_0 類型的,表明我是一個新的消息的起始。
EasyRTMP實現的重連過程
EasyRTMP對librtmp實現了一層封裝,不斷會檢測librtmp直播推送當前的RTMP連接狀態,當檢測到librtmp連接與RTMP流媒體服務器斷開的時候,我們會重新進行整個RTMP的握手、Connect、metadata的流程,這樣就能夠保證在沒有人為干預的情況下,EasyRTMP對外能提供穩定的推送服務,外部只需要考慮將生產者生產的音視頻數據源源不斷地往EasyRTMP接口進行推送就可以了,而且EasyRTMP的音視頻推送接口也是采用的異步模式,內部線程進行發送處理,這樣就保證了外圍調用者的工作效率,不會像直接調用librtmp那樣,在網絡較差的情況下,會出現發送阻塞等問題!
Github
https://github.com/EasyDarwin/EasyRTMP
獲取更多信息
郵件:support@easydarwin.org
WEB:www.EasyDarwin.org
Copyright ? EasyDarwin.org 2012-2016
總結
以上是生活随笔為你收集整理的EasyRTMP实现的rtmp推流的基本协议流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RTMPdump(libRTMP) 源代
- 下一篇: 最简单的基于librtmp的示例:发布H