直播推流实现RTMP协议的一些注意事项
018年8月4日第三次更新,詳細介紹了RTMP協議與遇到的坑,另外純Java重寫了RTMP協議,做了個Android 推流項目,包含安卓相機采集,編碼和RTMP推流,上傳到github了。
項目地址:https://github.com/gezhaoyou/SimpleLivePublisherLite
參考文章:
附:RTMP 協議整理成腦圖,比較清晰,包括rtmp 消息類型,rtmp 分塊chunking,rtmp分塊例子。免費腦圖工具 Xmind 格式,Here Get It
rtmp 消息類型
?Paste_Image.png
rtmp 消息分塊
?Paste_Image.png
1. 簡介
RTMP協議是Real Time Message Protocol(實時信息傳輸協議)的縮寫,它是由Adobe公司提出的一種應用層的協議,用來解決多媒體數據傳輸流的多路復用(Multiplexing)和分包(packetizing)的問題。隨著VR技術的發展,視頻直播等領域逐漸活躍起來,RTMP作為業內廣泛使用的協議也重新被相關開發者重視起來。本文主要分享對RTMP的一些簡介和實際開發中遇到的一些狀況。
RTMP協議基本特點:
? 基于TCP協議的應用層協議
? 默認通信端口1935
RTMP URL格式:
rtmp://ip:[port]/appName/streamName
例如: rtmp://192.168.178.218:1935/live/devzhaoyou
參考:https://blog.csdn.net/ai2000ai/article/details/72771461
2. RTMP 握手
RTMP 握手分為簡單握手和復雜握手,現在Adobe公司使用RTMP協議的產品用復雜握手的較多,不做介紹。
握手包格式:
0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ | version | +-+-+-+-+-+-+-+-+C0 and S0 bitsC0和S0:1個字節,包含了RTMP版本, 當前RTMP協議的版本為 3
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time (4 bytes) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | zero (4 bytes) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | random bytes | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | random bytes | | (cont) | | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+C1 and S1 bitsC1和S1:4字節時間戳,4字節的0,1528字節的隨機數
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| time (4 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| time2 (4 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| random echo |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| random echo || (cont) || .... |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+C2 and S2 bitsC2和S2:4字節時間戳,4字節從對端讀到的時間戳,1528字節隨機數
RTMP握手基本過程:
+-------------+ +-------------+ | Client | TCP/IP Network | Server | +-------------+ | +-------------+| | | Uninitialized | Uninitialized| C0 | ||------------------->| C0 || |-------------------->|| C1 | ||------------------->| S0 || |<--------------------|| | S1 |Version sent |<--------------------|| S0 | ||<-------------------| || S1 | ||<-------------------| Version sent| | C1 || |-------------------->|| C2 | ||------------------->| S2 || |<--------------------|Ack sent | Ack Sent| S2 | ||<-------------------| || | C2 || |-------------------->| Handshake Done | Handshake Done| | |Pictorial Representation of Handshake握手開始于客戶端發送C0、C1塊。服務器收到C0或C1后發送S0和S1。
當客戶端收齊S0和S1后,開始發送C2。當服務器收齊C0和C1后,開始發送S2。
當客戶端和服務器分別收到S2和C2后,握手完成。
注意事項: 在實際工程應用中,一般是客戶端先將C0, C1塊同時發出,服務器在收到C1 之后同時將S0, S1, S2發給客戶端。S2的內容就是收到的C1塊的內容。之后客戶端收到S1塊,并原樣返回給服務器,簡單握手完成。按照RTMP協議個要求,客戶端需要校驗C1塊的內容和S2塊的內容是否相同,相同的話才徹底完成握手過程,實際編寫程序用一般都不去做校驗。
RTMP握手的這個過程就是完成了兩件事:
校驗客戶端和服務器端RTMP協議版本號
是發了一堆隨機數據,校驗網絡狀況。
3. RTMP 消息
RTMP消息格式:
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Message Type | Payload length || (1 byte) | (3 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Timestamp || (4 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Stream ID || (3 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Message Header? 1字節消息類型
? 3字節負載消息長度
? 4字節時間戳
? 3字節 Stream ID,區分消息流
注意事項: 實際RTMP通信中并未按照上述格式去發送RTMP消息,而是將RTMP 消息分塊發送,之后將介紹RTMP消息分塊。
3.1. RTMP 消息分塊(chunking)
而對于基于TCP的RTMP協議而言,協議顯得特別繁瑣,但是有沒有更好的替代方案。同時創建RTMP消息分塊是比較復雜的地方,涉及到了AFM(也是Adobe家的東西)格式數據的數據。
RTMP消息塊格式:
+--------------+----------------+--------------------+--------------+| Basic Header | Message Header | Extended Timestamp | Chunk Data |+--------------+----------------+--------------------+--------------+| ||<------------------- Chunk Header ----------------->|Chunk FormatRTMP消息塊構成:
? Basic Header
? Message Header
? Extended Timestamp
? Chunk Data
Chunk Basic header格式有3種:
格式1:
0 1 2 3 4 5 6 7+-+-+-+-+-+-+-+-+|fmt| cs id |+-+-+-+-+-+-+-+-+Chunk basic header 1格式2:
0 10 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|fmt| 0 | cs id - 64 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Chunk basic header 2格式3:
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 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Chunk basic header 3注意事項:
fmt: 用于指定Chunk Header 里面 Message Header的類型,后面會介紹到
cs id: 是chunk stream id的縮寫,同一個RTMP消息拆成的 chunk 塊擁有相同的 cs id, 用于區分chunk所屬的RTMP消息, chunk basic header 的類型cs id占用的字節數來確定
Message Header格式:
Message Header的類型通過上文chunk basic header中的fmt指定,共4種:
格式0:
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| timestamp | message length|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| message length (cont) |message type id| msg stream id |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| message stream id (cont) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Chunk Message Header - Type 0Message Header占用11個字節, 在chunk stream的開始的第一個chunk的時候必須采用這種格式。
? timestamp:3個字節,因此它最多能表示到16777215=0xFFFFFF=2^24-1, 當它的值超過這個最大值時,這三個字節都置為1,實際的timestamp會轉存到Extended Timestamp字段中,接受端在判斷timestamp字段24個位都為1時就會去Extended timestamp中解析實際的時間戳。
? message length:3個字節,表示實際發送的消息的數據如音頻幀、視頻幀等數據的長度,單位是字節。注意這里是Message的長度,也就是chunk屬于的Message的總數據長度,而不是chunk本身Data的數據的長度。
? message type id:1個字節,表示實際發送的數據的類型,如8代表音頻數據、9代表視頻數據。
? msg stream id:4個字節,表示該chunk所在的流的ID,和Basic Header的CSID一樣,它采用小端存儲的方式
格式1:
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| timestamp | message length|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| message length (cont) |message type id| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Chunk Message Header - Type 1Message Header占用7個字節,省去了表示msg stream id的4個字節,表示此chunk和上一次發的chunk所在的流相同。
? timestamp delta:3個字節,注意這里和格式0時不同,存儲的是和上一個chunk的時間差。類似上面提到的timestamp,當它的值超過3個字節所能表示的最大值時,三個字節都置為1,實際的時間戳差值就會轉存到Extended Timestamp字段中,接受端在判斷timestamp delta字段24個位都為1時就會去Extended timestamp中解析時機的與上次時間戳的差值。
格式2:
0 1 2 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Chunk Message Header - Type 2Message Header占用3個字節,相對于格式1,又省去了表示消息長度的3個字節和表示消息類型的1個字節,表示此chunk和上一次發送的chunk所在的流、消息的長度和消息的類型都相同。余下的這三個字節表示timestamp delta,使用同格式1。
格式3:
0字節,它表示這個chunk的Message Header和上一個是完全相同的,無需再次傳送
Extended Timestamp(擴展時間戳):
在chunk中會有時間戳timestamp和時間戳差timestamp delta,并且它們不會同時存在,只有這兩者之一大于3個字節能表示的最大數值0xFFFFFF=16777215時,才會用這個字段來表示真正的時間戳,否則這個字段為0。
擴展時間戳占4個字節,能表示的最大數值就是0xFFFFFFFF=4294967295。當擴展時間戳啟用時,timestamp字段或者timestamp delta要全置為1,表示應該去擴展時間戳字段來提取真正的時間戳或者時間戳差。注意擴展時間戳存儲的是完整值,而不是減去時間戳或者時間戳差的值。
Chunk Data(塊數據): 用戶層面上真正想要發送的與協議無關的數據,長度在(0,chunkSize]之間, chunk size默認為128字節。
RTMP 消息分塊注意事項
? Chunk Size:
RTMP是按照chunk size進行分塊,chunk size 指的是 chunk的payload部分的大小,不包括chunk basic header 和 chunk message header長度。客戶端和服務器端各自維護了兩個chunk size, 分別是自身分塊的chunk size 和 對端 的chunk size, 默認的這兩個chunk size都是128字節。通過向對端發送set chunk size 消息可以告知對方更改了 chunk size的大小。
? Chunk Type:
RTMP消息分成的Chunk有4種類型,可以通過 chunk basic header的高兩位(fmt)指定,一般在拆包的時候會把一個RTMP消息拆成以格式0開始的chunk,之后的包拆成格式3 類型的chunk,我查看了有不少代碼也是這樣實現的,這樣也是最簡單的實現。
如果第二個message和第一個message的message stream ID 相同,并且第二個message的長度也大于了chunk size,那么該如何拆包?當時查了很多資料,都沒有介紹。后來看了一些源碼,如 SRS,FFMPEG中的實現,發現第二個message可以拆成Type_1類型一個chunk, message剩余的部分拆成Type_3類型的chunk。FFMPEG中就是這么做的。
3.2 RTMP 交互消息
推流RTMP消息交互流程:
-
pic_2.png
關于推流的過程,RTMP的協議文檔上給了上圖示例,說一下推流注意事項:
3.2.1 Connect 消息
RTMP 命令消息格式:
+----------------+---------+---------------------------------------+| Field Name | Type | Description |+--------------- +---------+---------------------------------------+| Command Name | String | Name of the command. Set to "connect".|+----------------+---------+---------------------------------------+| Transaction ID | Number | Always set to 1. |+----------------+---------+---------------------------------------+| Command Object | Object | Command information object which has || | | the name-value pairs. |+----------------+---------+---------------------------------------+| Optional User | Object | Any optional information || Arguments | | |+----------------+---------+---------------------------------------+RTMP握手之后先發送一個connect命令消息,命令里面包含什么東西,協議中沒有具體規定,實際通信中要攜帶 rtmp url 中的 appName 字段,并且指定一些編解碼的信息,并以AMF格式發送, 下面是用wireshake抓取connect命令需要包含的參數信息:
-
pic_3.png
這些信息協議中并沒有特別詳細說明, 在librtmp,srs-librtmp這些源碼中,以及用wireshark 抓包的時候可以看到。
服務器返回的是一個_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消息,沒有錯誤就可以進行下一步了。
3.2.2 Create Stream 消息
創建完RTMP連接之后就可以創建RTMP流,客戶端要想服務器發送一個releaseStream命令消息,之后是FCPublish命令消息,在之后是createStream命令消息。
當發送完createStream消息之后,解析服務器返回的消息會得到一個stream ID。
-
pic_4.png
這個ID也就是以后和服務器通信的 message stream ID, 一般返回的是1,不固定。
3.2.3 Publish Stream
推流準備工作的最后一步是Publish Stream,即向服務器發一個publish命令消息,消息中會帶有流名稱字段,即rtmp url中的 streamName,這個命令的message stream ID 就是上面 create stream 之后服務器返回的stream ID,發完這個命令一般不用等待服務器返回的回應,直接發送音視頻類型的RTMP數據包即可。有些rtmp庫還會發setMetaData消息,這個消息可以發也可以不發,里面包含了一些音視頻meta data的信息,如視頻的分辨率等等。
整個推流過程rtmp 消息抓包
-
rtmp_pulish_message.png
4. 推送音視頻
當以上工作都完成的時候,就可以發送音視頻了。音視頻RTMP消息的Payload(消息體)中都放的是按照FLV-TAG格式封的音視頻包,具體可以參照FLV封裝的協議文檔。格式必須封裝正確,否則會造成播放端不能正常拿到音視頻數據,無法播放音視頻。
5. 關于RTMP的時間戳
RTMP的時間戳單位是毫秒ms,在發送音視頻之前一直為零,發送音視頻消息包后時候必須保證時間戳是單調遞增的,時間戳必須打準確,否則播放端可能出現音視頻不同步的情況。Srs-librtmp的源碼中,如果推的是視頻文件的話,發現他們是用H264的dts作為時間戳的。實時音視頻傳輸的時候是先獲取了下某一時刻系統時間作為基準,然后每次相機采集到的視頻包,與起始的基準時間相減,得到時間戳,這樣可以保證時間戳的正確性。
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 類型的,表明新的消息的起始。
總結:
RTMP協議是個比較啰嗦的協議,實現起來也比較復雜,但通信過程過程相對簡單。在直播的實際工程應用中,協議上很多地方都沒有詳細說明,注意了以上提到幾點,基本能夠保證RTMP音視頻的通信正常。以上就是對RTMP協議的簡介和一些注意事項,希望能幫到有需要的朋友,另外本文難免有錯誤或說的不夠詳細的地方,歡迎指正,一起交流探討。
本篇文章2017年版本
前一段時間寫過一篇文章: iOS直播視頻數據采集、硬編碼保存h264文件,比較詳細的記錄了在做iOS端進行視頻數據采集和編碼的過程,下一步要做的就是RTMP協議推流。因為在公司將RTMP協議用Java 和 Swift 分別實現了一遍,所以對這塊比較了解,中間遇到了不少坑,記錄下來也怕自己忘掉。
RTMP協議是 Adobe 公司開發的一個基于TCP的應用層協議,Adobe 公司也公布了關于RTMP的規范,但是這個協議規范介紹的有些地方非常模糊,很多東西和實際應用是有差別的。網上也有不少關于這個協議的介紹,但都不是太詳細。我遇到的比較好的參考資料就是這篇:帶你吃透RTMP, 這篇文章只是在理論上對RTMP進行了比較詳細的解釋,很多東西還是和實際應用有出入。我這篇文章只是把遇到的一些坑記錄下來,并不是詳解RTMP消息的。
另外懂RTMP消息拆包分包,而不真正的寫寫的話是很難把RTMP協議弄得的很清楚,關于RTMP協議的實現也是比較麻煩的事,懂和做事兩回事。
另外用wireshark 抓一下包的話可以非常直觀的看到RTMP通信的過程,對理解RTMP非常有幫助,在調試代碼的時候也大量借助wireshark排錯,是一個非常有用的工具。
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 消息
關于推流的過程,RTMP的協議文檔上給了一個示例,而真實的RTMP通信過程和它有較大的差異,只說推流,RTMP播放端我沒有做過。
Connect消息
握手之后先發送一個connect 命令消息,命令里面包含什么東西,協議中沒有說,真實通信中要指定一些編解碼的信息,這些信息是以AMF格式發送的, 下面是用swift 寫的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)這些信息具體什么意思我也不太明白,協議中也沒有,都是我在看librtmp,srs-librtmp這些源碼,以及用wireshark 抓包的時候看到的。其中參數少一兩個貌似也沒問題,但是audioCodecs和videoCodecs這兩個指定音視頻編碼信息的不能少。
服務器返回的是一個_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 類型的,表明我是一個新的消息的起始。
作者:devzhaoyou
鏈接:https://www.jianshu.com/p/00aceabce944
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
總結
以上是生活随笔為你收集整理的直播推流实现RTMP协议的一些注意事项的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 流媒体相关知识点
- 下一篇: nginx-rtmp message