RTMP協議規範(轉載)

譯序:

        本文是爲截至發稿時止最新 Adobe 官方公佈的 RTMP 規範。本文包含 RTMP 規範的全部內容。是第一個比較全面的 RTMP 規範的中譯本。由於成文時間倉促,加上作者知識面所限,翻譯錯誤之處在所難免,懇請各位朋友熱心指出,可以直接在博客後面留言,先行謝過。rtmp_specification_1.0.pdf 官方下載地址http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf,國內下載地址http://download.csdn.net/detail/defonds/6312051。請隨時關注官方文檔更新:http://www.adobe.com/cn/devnet/rtmp.html。以下內容來自 rtmp_specification_1.0.pdf。

        

1. 簡介

        Adobe 公司的實時消息傳輸協議 (RTMP) 通過一個可靠地流傳輸提供了一個雙向多通道消息服務,比如 TCP [RFC0793],意圖在通信端之間傳遞帶有時間信息的視頻、音頻和數據消息流。實現通常對不同類型的消息分配不同的優先級,當運載能力有限時,這會影響等待流傳輸的消息的次序。
        本文檔將對實時流傳輸協議 (Real Time Messaging Protocol) 的語法和操作進行描述。

        

1.1. 術語

        本文檔中出現的關鍵字,"MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"NOT RECOMMENDED"、"MAY" 、"OPTIONAL",都將在 [RFC2119] 中進行解釋。

        

2. 貢獻者

        Rajesh Mallipeddi,Adobe Systems 原成員,起草了本文檔原始規範,並提供大部分的原始內容。
        Mohit Srivastava,Adobe Systems 成員,促成了本規範的開發。

        

3. 名詞解釋

        Payload (有效載荷):包含於一個數據包中的數據,例如音頻採樣或者壓縮的視頻數據。payload 的格式和解釋,超出了本文檔的範圍。
        Packet (數據包):一個數據包由一個固定頭和有效載荷數據構成。一些個底層協議可能會要求對數據包定義封裝。
        Port (端口):"傳輸協議用以區分開指定一臺主機的不同目的地的一個抽象。TCP/IP 使用小的正整數對端口進行標識。" OSI 傳輸層使用的運輸選擇器 (TSEL) 相當於端口。
        Transport address (傳輸地址):用以識別傳輸層端點的網絡地址和端口的組合,例如一個 IP 地址和一個 TCP 端口。數據包由一個源傳輸地址傳送到一個目的傳輸地址。
        Message stream (消息流):通信中消息流通的一個邏輯通道。
        Message stream ID (消息流 ID):每個消息有一個關聯的 ID,使用 ID 可以識別出流通中的消息流。
        Chunk (塊):消息的一段。消息在網絡發送之前被拆分成很多小的部分。塊可以確保端到端交付所有消息有序 timestamp,即使有很多不同的流。
        Chunk stream (塊流):通信中允許塊流向一個特定方向的邏輯通道。塊流可以從客戶端流向服務器,也可以從服務器流向客戶端。
        Chunk stream ID (塊流 ID):每個塊有一個關聯的 ID,使用 ID 可以識別出流通中的塊流。
        Multiplexing (合成):將獨立的音頻/視頻數據合成爲一個連續的音頻/視頻流的加工,這樣可以同時發送幾個視頻和音頻。
        DeMultiplexing (分解):Multiplexing 的逆向處理,將交叉的音頻和視頻數據還原成原始音頻和視頻數據的格式。
        Remote Procedure Call (RPC 遠程方法調用):允許客戶端或服務器調用對端的一個子程序或者程序的請求。
        Metadata (元數據):關於數據的一個描述。一個電影的 metadata 包括電影標題、持續時間、創建時間等等。
        Application Instance (應用實例):服務器上應用的實例,客戶端可以連接這個實例併發送連接請求。
        Action Message Format (AMF 動作消息格式協議):一個用於序列化 ActionScript 對象圖的緊湊的二進制格式。AMF 有兩個版本:AMF 0 [AMF0] 和 AMF 3 [AMF3]。

        

4. 字節序、對齊和時間格式

        所有整數型屬性以網絡字節順序傳輸,字節 0 代表第一個字節,零位是一個單詞或字段最常用的有效位。字節序通常是大端排序。關於傳輸順序的更多細節描述參考 IP 協議 [RFC0791]。除非另外註明,本文檔中的數值常量都是十進制的 (以 10 爲基礎)。
        除非另有規定,RTMP 中的所有數據都是字節對準的;例如,一個十六位的屬性可能會在一個奇字節偏移上。填充後,填充字節應該有零值。
        RTMP 中的 Timestamps 以一個整數形式給出,表示一個未指明的時間點。典型地,每個流會以一個爲 0 的 timestamp 起始,但這不是必須的,只要雙端能夠就時間點達成一致。注意這意味着任意不同流 (尤其是來自不同主機的) 的同步需要 RTMP 之外的機制。
        因爲 timestamp 的長度爲 32 位,每隔 49 天 17 小時 2 分鐘和 47.296 秒就要重來一次。因爲允許流連續傳輸,有可能要多年,RTMP 應用在處理 timestamp 時應該使用序列碼算法 [RFC1982],並且能夠處理無限循環。例如,一個應用假定所有相鄰的 timestamp 都在 2^31 - 1 毫秒之內,因此 10000 在 4000000000 之後,而 3000000000 在 4000000000 之前。
        timestamp 也可以使用無符整數定義,相對於前面的 timestamp。timestamp 的長度可能會是 24 位或者 32 位。

        

5. RTMP 塊流

        本節介紹實時消息傳輸協議的塊流 (RTMP 塊流)。 它爲上層多媒體流協議提供合併和打包的服務。
        當設計 RTMP 塊流使用實時消息傳輸協議時,它可以處理任何發送消息流的協議。每個消息包含 timestamp 和 payload 類型標識。RTMP塊流和RTMP一起適合各種音頻-視頻應用,從一對一和一對多直播到點播服務,到互動會議應用
        當使用可靠傳輸協議時,比如 TCP [RFC0793],RTMP 塊流能夠對於多流提供所有消息可靠的 timestamp 有序端對端傳輸。RTMP 塊流並不提供任何優先權或類似形式的控制,但是可以被上層協議用來提供這種優先級。例如,一個直播視頻服務器可能會基於發送時間或者每個消息的確認時間丟棄一個傳輸緩慢的客戶端的視頻消息以確保及時獲取其音頻消息。
        RTMP 塊流包括其自身的帶內協議控制信息,並且提供機制爲上層協議植入用戶控制消息。

       

5.1 消息格式

        可以被分割爲塊以支持組合的消息的格式取決於上層協議。消息格式必須包含以下創建塊所需的字段。
        Timestamp:消息的 timestamp。這個字段可以傳輸四個字節。
        Length:消息的有效負載長度。如果不能省略掉消息頭,那它也被包括進這個長度。這個字段佔用了塊頭的三個字節。
        Type Id:一些類型 ID 保留給協議控制消息使用。這些傳播信息的消息由 RTMP 塊流協議和上層協議共同處理。其他的所有類型 ID 可用於上層協議,它們被 RTMP 塊流處理爲不透明值。事實上,RTMP 塊流中沒有任何地方要把這些值當做類型使用;所有消息必須是同一類型,或者應用使用這一字段來區分同步跟蹤,而不是類型。這一字段佔用了塊頭的一個字節。
        Message Stream ID:message stream (消息流) ID 可以使任意值。合併到同一個塊流的不同的消息流是根據各自的消息流 ID 進行分解。除此之外,對 RTMP 塊流而言,這是一個不透明的值。這個字段以小端格式佔用了塊頭的四個字節。

        

5.2 簡單握手(simple handshake)

        這裏主要討論的是簡單握手協議。simple handshake是rtmp spec 1.0定義的握手方式。而complex handshake是後來變更的方式,Adobe沒有公開。在此暫不討論。

       一個 RTMP 連接以握手開始。RTMP 的握手不同於其他協議;RTMP 握手由三個固定長度的塊組成,而不是像其他協議一樣的帶有報頭的可變長度的塊。
        客戶端 (發起連接請求的終端) 和服務器端各自發送相同的三塊。便於演示,當發送自客戶端時這些塊被指定爲 C0、C1 和 C2;當發送自服務器端時這些塊分別被指定爲 S0、S1 和 S2。

        5.2.1. 握手順序

        握手以客戶端發送 C0 和 C1 塊開始
        客戶端必須等待接收到 S1 才能發送 C2
        客戶端必須等待接收到 S2 才能發送任何其他數據
        服務器端必須等待接收到 C0 才能發送 S0 和 S1,也可以等待接收到 C1 再發送 S0 和 S1。服務器端必須等待接收到 C1 才能發送 S2。服務器端必須等待接收到 C2 才能發送任何其他數據。

        5.2.2. C0 和 S0 的格式

        C0 和 S0 包都是一個單一的八位字節,以一個單獨的八位整型域進行處理:
C0 and S0 bits
        以下是 C0/S0 包中的字段:

        版本號 (八位):在 C0 中,這一字段指示出客戶端要求的 RTMP 版本號。在 S0 中,這一字段指示出服務器端選擇的 RTMP 版本號。本文檔中規範的版本號爲 3。0、1、2 三個值是由早期其他產品使用的,是廢棄值;4 - 31 被保留爲 RTMP 協議的未來實現版本使用;32 - 255 不允許使用(以區分開 RTMP 和其他常以一個可打印字符開始的文本協議)。無法識別客戶端所請求版本號的服務器應該以版本 3 響應,(收到響應的) 客戶端可以選擇降低到版本 3,或者放棄握手。

        如上圖,是一個c0+c1一起發送的抓包例子(complex handshake的)。其中選中的字節爲c0字段,代表版本號。後面的數據是c1字段。整體包長1537.

        5.2.3. C1 和 S1 的格式

        C1 和 S1 數據包的長度都是 1536 字節,包含以下字段:
C1 and S1 bits
        Time (四個字節):這個字段包含一個 timestamp,用於本終端發送的所有後續塊的時間起點。這個值可以是 0,或者一些任意值。要同步多個塊流,終端可以發送其他塊流當前的 timestamp 的值
        Zero (四個字節):這個字段必須都是 0。
        Random data (1528 個字節):這個字段可以包含任意值。終端需要區分出響應來自它發起的握手還是對端發起的握手,這個數據應該發送一些足夠隨機的數。這個不需要對隨機數進行加密保護,也不需要動態值。

        5.2.4. C2 和 S2 的格式

        C2 和 S2 數據包長度都是 1536 字節,基本就是 S1 和 C1 的副本 (分別),包含有以下字段:
C2 and S2 bits
        Time (四個字節):這個字段必須包含終端在 S1 (給 C2) 或者 C1 (給 S2) 發的 timestamp。
        Time2 (四個字節):這個字段必須包含終端先前發出數據包 (s1 或者 c1) timestamp。
        Random echo (1528 個字節):這個字段必須包含終端發的 S1 (給 C2) 或者 S2 (給 C1) 的隨機數。兩端都可以一起使用 time 和 time2 字段再加當前 timestamp 以快速估算帶寬和/或者連接延遲,但這不太可能是有多大用處。

        5.2.5. 握手示意圖

Pictorial Representation of Handshake
        下面描述了握手示意圖中提到的狀態:
        Uninitialized (未初始化):協議的版本號在這個階段被髮送。客戶端和服務器都是 uninitialized (未初始化) 狀態。之後客戶端在數據包 C0 中將協議版本號發出。如果服務器支持這個版本,它將在迴應中發送 S0 和 S1。如果不支持呢,服務器會纔去適當的行爲進行響應。在 RTMP 協議中,這個行爲就是終止連接。
        Version Sent (版本已發送):在未初始化狀態之後,客戶端和服務器都進入 Version Sent (版本已發送) 狀態。客戶端會等待接收數據包 S1 而服務器在等待 C1。一旦拿到期待的包,客戶端會發送數據包 C2 而服務器發送數據包 S2。(客戶端和服務器各自的)狀態隨即變爲 Ack Sent (確認已發送)。
        Ack Sent (確認已發送):客戶端和服務器分別等待 S2 和 C2。
        Handshake Done (握手結束):客戶端和服務器可以開始交換消息了。

        

 

5.3 複雜握手(complex handshake)

        rtmp 1.0規範中,指定了RTMP的握手協議:
  • c0/s0:一個字節,說明是明文還是加密。
  • c1/s1: 1536字節,4字節時間,4字節0x00,1528字節隨機數
  • c2/s2: 1536字節,4字節時間1,4字節時間2,1528隨機數和s1相同。這個就是srs以及其他開源軟件所謂的simple handshake,簡單握手,標準握手,FMLE也是使用這個握手協議。
        Flash播放器連接服務器時,若服務器只支持簡單握手,則無法播放h264和aac的流,可能是adobe的限制。adobe將簡單握手改爲了有一系列加密算法的複雜握手(complex handshake)。下表是總結:

 

        由上表可知,當服務器和客戶端的握手是按照rtmp協議進行,是不支持h264/aac的,有數據,就是沒有視頻和聲音。原因是adobe變更了握手的數據結構,標準rtmp協議的握手的包是隨機的1536字節(S1S2C1C2),變更後的是需要進行摘要和加密。rtmp協議定義的爲simple handshake,變更後加密握手可稱爲complex handshake。

        本文詳細分析了rtmpd(ccrtmpserver)中的處理邏輯,以及rtmpdump的處理邏輯,從一個全是魔法數字的世界找到他們的數據結構和算法。

 

        5.3.1. complex handshake C1 S1結構(此處參照了winlin博客的相關文章)

        complex handshake將C1S1分爲4個部分,它們的順序(schema)一種可能是:

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. // c1s1 schema0  
  2. time: 4bytes  
  3. version: 4bytes  
  4. key: 764bytes  
  5. digest: 764bytes  

        其中,key和digest可能會交換位置,即schema可能是:

 

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. // c1s1 schema1  
  2. time: 4bytes  
  3. version: 4bytes  
  4. digest: 764bytes  
  5. key: 764bytes  

        客戶端來決定使用哪種schema,服務器端則需要先嚐試按照schema0解析,失敗則用schema1解析。如下圖所示:

 

        無論key和digest位置如何,它們的結構是不變的:

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. // 764bytes key結構  
  2. random-data: (offset)bytes  
  3. key-data: 128bytes  
  4. random-data: (764-offset-128-4)bytes  
  5. offset: 4bytes  
  6.   
  7. // 764bytes digest結構  
  8. offset: 4bytes  
  9. random-data: (offset)bytes  
  10. digest-data: 32bytes  
  11. random-data: (764-4-offset-32)bytes  

 

備註:發現FMS只認識digest-key結構。

如下圖所示:

crtmp中這些全是魔法數字。

 

        5.3.2.complex handshake  C2 S2結構:

c2 s2主要是用來提供對C1 S1的驗證,結構如下:

 

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. // 1536bytes C2S2結構  
  2. random-data: 1504bytes  
  3. digest-data: 32bytes  
C2S2的結構相對比較簡單。如下圖所示:

下面介紹C1S1C2S2的生成以及驗證算法。

        5.3.3.complex handshake  C1 S1算法

C1S1中都是包含32字節的digest,而且digest將C1S1分成兩部分:

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. // C1S1被digest分成兩部分  
  2. c1s1-part1: n bytes  
  3. digest-data: 32bytes  
  4. c1s1-part2: (1536-n-32)bytes  

 

如下圖所示:

            

在生成C1時,需要用到c1s1-part1和c1s1-part2這兩個部分的字節拼接起來的字節,定義爲:

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. c1s1-joined = bytes_join(c1s1-part1, c1s1-part2)  

 

也就是說,把1536字節的c1s1中的32字節的digest拿剪刀剪掉,剩下的頭和尾加在一起就是c1s1-joined。用到的兩個常量FPKey和FMSKey:

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. u_int8_t FMSKey[] = {  
  2.     0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,  
  3.     0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,  
  4.     0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,  
  5.     0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,  
  6.     0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001  
  7.     0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,  
  8.     0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,  
  9.     0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,  
  10.     0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae  
  11. }; // 68  
  12.   
  13. u_int8_t FPKey[] = {  
  14.     0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,  
  15.     0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,  
  16.     0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,  
  17.     0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001  
  18.     0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,  
  19.     0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,  
  20.     0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,  
  21.     0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE  
  22. }; // 62  

生成C1的算法如下:

 

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. calc_c1_digest(c1, schema) {  
  2.     get c1s1-joined from c1 by specified schema  
  3.     digest-data = HMACsha256(c1s1-joined, FPKey, 30)  
  4.     return digest-data;  
  5. }  
  6. random fill 1536bytes c1 // also fill the c1-128bytes-key  
  7. time = time() // c1[0-3]  
  8. version = [0x80, 0x00, 0x07, 0x02] // c1[4-7]  
  9. schema = choose schema0 or schema1  
  10. digest-data = calc_c1_digest(c1, schema)  
  11. copy digest-data to c1  
生成S1的算法如下:

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. /*decode c1 try schema0 then schema1*/  
  2. c1-digest-data = get-c1-digest-data(schema0)  
  3. if c1-digest-data equals to calc_c1_digest(c1, schema0) {  
  4.     c1-key-data = get-c1-key-data(schema0)  
  5.     schema = schema0  
  6. else {  
  7.     c1-digest-data = get-c1-digest-data(schema1)  
  8.     if c1-digest-data not equals to calc_c1_digest(c1, schema1) {  
  9.         switch to simple handshake.  
  10.         return  
  11.     }  
  12.     c1-key-data = get-c1-key-data(schema1)  
  13.     schema = schema1  
  14. }  
  15.   
  16. /*generate s1*/  
  17. random fill 1536bytes s1  
  18. time = time() // c1[0-3]  
  19. version = [0x04, 0x05, 0x00, 0x01] // s1[4-7]  
  20. DH_compute_key(key = c1-key-data, pub_key=s1-key-data)  
  21. get c1s1-joined by specified schema  
  22. s1-digest-data = HMACsha256(c1s1-joined, FMSKey, 36)  
  23. copy s1-digest-data and s1-key-data to s1.  
C1S1的算法完畢。

        5.3.4.complex handshake  C2 S2

C2S2的生成算法如下:

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. random fill c2s2 1536 bytes  
  2.   
  3. // client generate C2, or server valid C2  
  4. temp-key = HMACsha256(s1-digest, FPKey, 62)  
  5. c2-digest-data = HMACsha256(c2-random-data, temp-key, 32)  
  6.   
  7. // server generate S2, or client valid S2  
  8. temp-key = HMACsha256(c1-digest, FMSKey, 68)  
  9. s2-digest-data = HMACsha256(s2-random-data, temp-key, 32)  
驗證的算法是一樣的。

 

 

        5.3.5.解析補充

讀rtmpd(ccrtmpserver)代碼發現,handshake確實變更了。BTW:rtmpd的代碼可讀性要強很多,很快就能知道它在做什麼;不過,它是部分C++部分C的混合體;譬如,使用了BaseRtmpProtocol和InboundRtmpProtocol這種C++的解決方式;以及在解析complex handshake時對1536字節的包直接操作,offset=buf[772]+buf[773]+buf[774]+buf[775],這個就很難看明白在做什麼了,其實1536是由4字節的time+4字節的version+764字節的key+764字節的digest,key的offset在後面,digest的offset在前面,若定義兩個結構再讓它們自己去解析,就很明白在做什麼了。

 

sources\thelib\src\protocols\rtmp\inboundrtmpprotocol.cpp: 51

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. InboundRTMPProtocol::PerformHandshake  
  2.     // 沒有完成握手。  
  3.     case RTMP_STATE_NOT_INITIALIZED:  
  4.         // buffer[1537]  
  5.         // 第一個字節,即c0,表示握手類型(03是明文,06是加密,其他值非法)  
  6.         handshakeType = GETIBPOINTER(buffer)[0];  
  7.         // 刪除第一個字節,buffer[1536] 即c1  
  8.         buffer.Ignore(1);  
  9.         // 第5-8共4個字節表示FPVersion,這個必須非0,0表示不支持complex handshake。  
  10.         _currentFPVersion = ENTOHLP(GETIBPOINTER(buffer) + 4);  
  11.         // 進行明文握手(false表示明文)  
  12.         PerformHandshake(buffer, false);  
  13.           
  14. InboundRTMPProtocol::PerformHandshake  
  15.     // 先驗證client,即驗證c1  
  16.     // 先嚐試scheme0  
  17.     valid = ValidClientScheme(0);  
  18.     // 若失敗,再嘗試scheme1  
  19.     valid = ValidClientScheme(1)  
  20.     // 若驗證成功  
  21.     if(valid)  
  22.         // 複雜的handshake:PerformComplexHandshake,主要流程如下:  
  23.         S0 = 3或6   
  24.         隨機數初始化S1S2  
  25.         根據C1的public key生成S1的128byets public key  
  26.         生成S1的32bytes digest  
  27.         根據C1和S2生成S2的32bytes digest   
  28.     else  
  29.         // rtmp spec 1.0定義的握手方式   
  30.         PerformSimpleHandshake();  

其實到後面看明白了,scheme1和scheme2這兩種方式,是包結構的調換。

 

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. <strong>complex的包結構如下:</strong>C1/S1 1536bytes  
  2.     time: 4bytes 開頭是4字節的當前時間。(u_int32_t)time(NULL)  
  3.     peer_version: 4bytes 爲程序版本。C1一般是0x80000702。S1是0x04050001。  
  4.     764bytes: 可能是KeyBlock或者DigestBlock  
  5.     764bytes: 可能是KeyBlock或者DigestBlock  
  6. 其中scheme1就是KeyBlock在前面DigestBlock在後面,而scheme0是DigestBlock在前面KeyBlock在後面。  
  7. <strong>子結構KeyBlock定義:</strong>  
  8.     760bytes: 包含128bytes的key的數據。  
  9.     key_offset: 4bytes 最後4字節定義了key的offset(相對於KeyBlock開頭而言)  
  10. <pre name="code" class="cpp"><strong>子結構DigestBlock定義:</strong>  
  11.     digest_offset: 4bytes 開頭4字節定義了digest的offset(相對於第DigestBlock的第5字節而言,offset=3表示digestBlock[7~38]爲digest  
  12.     760bytes: 包含32bytes的digest的數據。  

 

其中,key和digest的主要算法是:C1的key爲128bytes隨機數。C1_32bytes_digest = HMACsha256(P1+P2, 1504, FPKey, 30) ,其中P1爲digest之前的部分,P2爲digest之後的部分,P1+P2是將這兩部分拷貝到新的數組,共1536-32長度。S1的key根據C1的key算出來,算法如下:
[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. DHWrapper dhWrapper(1024);  
  2. dhWrapper.Initialize()  
  3. dhWrapper.CreateSharedKey(c1_key, 128)  
  4. dhWrapper.CopyPublicKey(s1_key, 128)  
S1的digest算法同C1。注意,必須先計算S1的key,因爲key變化後digest也也重新計算。
S2/C2沒有key,只有一個digest,是根據C1/S1算出來的:

 

 

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. 先用隨機數填充S2  
  2. s2data=S2[0-1504]; 前1502字節爲隨機的數據。  
  3. s2digest=S2[1505-1526] 後32bytes爲digest。  
  4. // 計算s2digest方法如下:  
  5. ptemphash[512]: HMACsha256(c1digest, 32, FMSKey, 68, ptemhash)  
  6. ps2hash[512]: HMACsha256(s2data, 1504, ptemphash, 32, ps2hash)  
  7. 將ps2hash[0-31]拷貝到s2的後32bytes。  

 

        5.3.6.代碼實現

[cpp] view plain copy
 
 
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* 
  2. The MIT License (MIT) 
  3.  
  4. Copyright (c) 2013 winlin 
  5.  
  6. Permission is hereby granted, free of charge, to any person obtaining a copy of 
  7. this software and associated documentation files (the "Software"), to deal in 
  8. the Software without restriction, including without limitation the rights to 
  9. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
  10. the Software, and to permit persons to whom the Software is furnished to do so, 
  11. subject to the following conditions: 
  12.  
  13. The above copyright notice and this permission notice shall be included in all 
  14. copies or substantial portions of the Software. 
  15.  
  16. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  17. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
  18. FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
  19. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
  20. IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  21. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
  22. */  
  23.   
  24. #ifndef SRS_CORE_COMPLEX_HANDSHKAE_HPP  
  25. #define SRS_CORE_COMPLEX_HANDSHKAE_HPP  
  26.   
  27. /* 
  28. #include <srs_core_complex_handshake.hpp> 
  29. */  
  30.   
  31. #include <srs_core.hpp>  
  32.   
  33. class SrsSocket;  
  34.   
  35. /** 
  36. * rtmp complex handshake, 
  37. * @see also crtmp(crtmpserver) or librtmp, 
  38. * @see also: http://blog.csdn.net/win_lin/article/details/13006803 
  39. * @doc update the README.cmd 
  40. */  
  41. class SrsComplexHandshake  
  42. {  
  43. public:  
  44. SrsComplexHandshake();  
  45. virtual ~SrsComplexHandshake();  
  46. public:  
  47. /** 
  48. * complex hanshake. 
  49. * @_c1, size of c1 must be 1536. 
  50. * @remark, user must free the c1. 
  51. * @return user must: 
  52. * continue connect app if success, 
  53. * try simple handshake if error is ERROR_RTMP_TRY_SIMPLE_HS, 
  54. * otherwise, disconnect 
  55. */  
  56. virtual int handshake(SrsSocket& skt, char* _c1);  
  57. };  
  58.   
  59. #endif  
  60.   
  61. /* 
  62. The MIT License (MIT) 
  63.  
  64. Copyright (c) 2013 winlin 
  65.  
  66. Permission is hereby granted, free of charge, to any person obtaining a copy of 
  67. this software and associated documentation files (the "Software"), to deal in 
  68. the Software without restriction, including without limitation the rights to 
  69. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
  70. the Software, and to permit persons to whom the Software is furnished to do so, 
  71. subject to the following conditions: 
  72.  
  73. The above copyright notice and this permission notice shall be included in all 
  74. copies or substantial portions of the Software. 
  75.  
  76. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  77. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
  78. FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
  79. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
  80. IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  81. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
  82. */  
  83.   
  84. #include <srs_core_complex_handshake.hpp>  
  85.   
  86. #include <time.h>  
  87. #include <stdlib.h>  
  88.   
  89. #include <srs_core_error.hpp>  
  90. #include <srs_core_log.hpp>  
  91. #include <srs_core_auto_free.hpp>  
  92. #include <srs_core_socket.hpp>  
  93.   
  94. // 68bytes FMS key which is used to sign the sever packet.  
  95. u_int8_t SrsGenuineFMSKey[] = {  
  96. 0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,  
  97. 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,  
  98. 0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,  
  99. 0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,  
  100. 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001  
  101. 0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,  
  102. 0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,  
  103. 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,  
  104. 0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae  
  105. }; // 68  
  106.   
  107. // 62bytes FP key which is used to sign the client packet.  
  108. u_int8_t SrsGenuineFPKey[] = {  
  109. 0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,  
  110. 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,  
  111. 0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,  
  112. 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001  
  113. 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,  
  114. 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,  
  115. 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,  
  116. 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE  
  117. }; // 62  
  118.   
  119. #include <openssl/evp.h>  
  120. #include <openssl/hmac.h>  
  121. int openssl_HMACsha256(const void* data, int data_size, const void* key, int key_size, void* digest) {  
  122. HMAC_CTX ctx;  
  123. HMAC_CTX_init(&ctx);  
  124. HMAC_Init_ex(&ctx, (unsigned char*) key, key_size, EVP_sha256(), NULL);  
  125. HMAC_Update(&ctx, (unsigned char *) data, data_size);  
  126.   
  127. unsigned int digest_size;  
  128. HMAC_Final(&ctx, (unsigned char *) digest, &digest_size);  
  129. HMAC_CTX_cleanup(&ctx);  
  130. if (digest_size != 32) {  
  131. return ERROR_OpenSslSha256DigestSize;  
  132. }  
  133. return ERROR_SUCCESS;  
  134. }  
  135.   
  136. #include <openssl/dh.h>  
  137. #define RFC2409_PRIME_1024 \  
  138. "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \  
  139. "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \  
  140. "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \  
  141. "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \  
  142. "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" \  
  143. "FFFFFFFFFFFFFFFF"  
  144. int __openssl_generate_key(  
  145. u_int8_t*& _private_key, u_int8_t*& _public_key, int32_t& size,  
  146. DH*& pdh, int32_t& bits_count, u_int8_t*& shared_key, int32_t& shared_key_length, BIGNUM*& peer_public_key  
  147. ){  
  148. int ret = ERROR_SUCCESS;  
  149.   
  150. //1. Create the DH  
  151. if ((pdh = DH_new()) == NULL) {  
  152. ret = ERROR_OpenSslCreateDH;  
  153. return ret;  
  154. }  
  155.   
  156. //2. Create his internal p and g  
  157. if ((pdh->p = BN_new()) == NULL) {  
  158. ret = ERROR_OpenSslCreateP;  
  159. return ret;  
  160. }  
  161. if ((pdh->g = BN_new()) == NULL) {  
  162. ret = ERROR_OpenSslCreateG;  
  163. return ret;  
  164. }  
  165.   
  166. //3. initialize p, g and key length  
  167. if (BN_hex2bn(&pdh->p, RFC2409_PRIME_1024) == 0) {  
  168. ret = ERROR_OpenSslParseP1024;  
  169. return ret;  
  170. }  
  171. if (BN_set_word(pdh->g, 2) != 1) {  
  172. ret = ERROR_OpenSslSetG;  
  173. return ret;  
  174. }  
  175.   
  176. //4. Set the key length  
  177. pdh->length = bits_count;  
  178.   
  179. //5. Generate private and public key  
  180. if (DH_generate_key(pdh) != 1) {  
  181. ret = ERROR_OpenSslGenerateDHKeys;  
  182. return ret;  
  183. }  
  184.   
  185. // CreateSharedKey  
  186. if (pdh == NULL) {  
  187. ret = ERROR_OpenSslGenerateDHKeys;  
  188. return ret;  
  189. }  
  190.   
  191. if (shared_key_length != 0 || shared_key != NULL) {  
  192. ret = ERROR_OpenSslShareKeyComputed;  
  193. return ret;  
  194. }  
  195.   
  196. shared_key_length = DH_size(pdh);  
  197. if (shared_key_length <= 0 || shared_key_length > 1024) {  
  198. ret = ERROR_OpenSslGetSharedKeySize;  
  199. return ret;  
  200. }  
  201. shared_key = new u_int8_t[shared_key_length];  
  202. memset(shared_key, 0, shared_key_length);  
  203.   
  204. peer_public_key = BN_bin2bn(_private_key, size, 0);  
  205. if (peer_public_key == NULL) {  
  206. ret = ERROR_OpenSslGetPeerPublicKey;  
  207. return ret;  
  208. }  
  209.   
  210. if (DH_compute_key(shared_key, peer_public_key, pdh) == -1) {  
  211. ret = ERROR_OpenSslComputeSharedKey;  
  212. return ret;  
  213. }  
  214.   
  215. // CopyPublicKey  
  216. if (pdh == NULL) {  
  217. ret = ERROR_OpenSslComputeSharedKey;  
  218. return ret;  
  219. }  
  220. int32_t keySize = BN_num_bytes(pdh->pub_key);  
  221. if ((keySize <= 0) || (size <= 0) || (keySize > size)) {  
  222. //("CopyPublicKey failed due to either invalid DH state or invalid call"); return ret;  
  223. ret = ERROR_OpenSslInvalidDHState;  
  224. return ret;  
  225. }  
  226.   
  227. if (BN_bn2bin(pdh->pub_key, _public_key) != keySize) {  
  228. //("Unable to copy key"); return ret;  
  229. ret = ERROR_OpenSslCopyKey;  
  230. return ret;  
  231. }  
  232. return ret;  
  233. }  
  234. int openssl_generate_key(char* _private_key, char* _public_key, int32_t size)  
  235. {  
  236. int ret = ERROR_SUCCESS;  
  237.   
  238. // Initialize  
  239. DH* pdh = NULL;  
  240. int32_t bits_count = 1024;  
  241. u_int8_t* shared_key = NULL;  
  242. int32_t shared_key_length = 0;  
  243. BIGNUM* peer_public_key = NULL;  
  244. ret = __openssl_generate_key(  
  245. (u_int8_t*&)_private_key, (u_int8_t*&)_public_key, size,  
  246. pdh, bits_count, shared_key, shared_key_length, peer_public_key  
  247. );  
  248. if (pdh != NULL) {  
  249. if (pdh->p != NULL) {  
  250. BN_free(pdh->p);  
  251. pdh->p = NULL;  
  252. }  
  253. if (pdh->g != NULL) {  
  254. BN_free(pdh->g);  
  255. pdh->g = NULL;  
  256. }  
  257. DH_free(pdh);  
  258. pdh = NULL;  
  259. }  
  260.   
  261. if (shared_key != NULL) {  
  262. delete[] shared_key;  
  263. shared_key = NULL;  
  264. }  
  265.   
  266. if (peer_public_key != NULL) {  
  267. BN_free(peer_public_key);  
  268. peer_public_key = NULL;  
  269. }  
  270.   
  271. return ret;  
  272. }  
  273.   
  274. // the digest key generate size.  
  275. #define OpensslHashSize 512  
  276.   
  277. /** 
  278. * 764bytes key結構 
  279. * random-data: (offset)bytes 
  280. * key-data: 128bytes 
  281. * random-data: (764-offset-128-4)bytes 
  282. * offset: 4bytes 
  283. */  
  284. struct key_block  
  285. {  
  286. // (offset)bytes  
  287. char* random0;  
  288. int random0_size;  
  289. // 128bytes  
  290. char key[128];  
  291. // (764-offset-128-4)bytes  
  292. char* random1;  
  293. int random1_size;  
  294. // 4bytes  
  295. int32_t offset;  
  296. };  
  297. // calc the offset of key,  
  298. // the key->offset cannot be used as the offset of key.  
  299. int srs_key_block_get_offset(key_block* key)  
  300. {  
  301. int max_offset_size = 764 - 128 - 4;  
  302. int offset = 0;  
  303. u_int8_t* pp = (u_int8_t*)&key->offset;  
  304. offset += *pp++;  
  305. offset += *pp++;  
  306. offset += *pp++;  
  307. offset += *pp++;  
  308.   
  309. return offset % max_offset_size;  
  310. }  
  311. // create new key block data.  
  312. // if created, user must free it by srs_key_block_free  
  313. void srs_key_block_init(key_block* key)  
  314. {  
  315. key->offset = (int32_t)rand();  
  316. key->random0 = NULL;  
  317. key->random1 = NULL;  
  318. int offset = srs_key_block_get_offset(key);  
  319. srs_assert(offset >= 0);  
  320. key->random0_size = offset;  
  321. if (key->random0_size > 0) {  
  322. key->random0 = new char[key->random0_size];  
  323. for (int i = 0; i < key->random0_size; i++) {  
  324. *(key->random0 + i) = rand() % 256;  
  325. }  
  326. }  
  327. for (int i = 0; i < (int)sizeof(key->key); i++) {  
  328. *(key->key + i) = rand() % 256;  
  329. }  
  330. key->random1_size = 764 - offset - 128 - 4;  
  331. if (key->random1_size > 0) {  
  332. key->random1 = new char[key->random1_size];  
  333. for (int i = 0; i < key->random1_size; i++) {  
  334. *(key->random1 + i) = rand() % 256;  
  335. }  
  336. }  
  337. }  
  338. // parse key block from c1s1.  
  339. // if created, user must free it by srs_key_block_free  
  340. // @c1s1_key_bytes the key start bytes, maybe c1s1 or c1s1+764  
  341. int srs_key_block_parse(key_block* key, char* c1s1_key_bytes)  
  342. {  
  343. int ret = ERROR_SUCCESS;  
  344.   
  345. char* pp = c1s1_key_bytes + 764;  
  346. pp -= sizeof(int32_t);  
  347. key->offset = *(int32_t*)pp;  
  348. key->random0 = NULL;  
  349. key->random1 = NULL;  
  350. int offset = srs_key_block_get_offset(key);  
  351. srs_assert(offset >= 0);  
  352. pp = c1s1_key_bytes;  
  353. key->random0_size = offset;  
  354. if (key->random0_size > 0) {  
  355. key->random0 = new char[key->random0_size];  
  356. memcpy(key->random0, pp, key->random0_size);  
  357. }  
  358. pp += key->random0_size;  
  359. memcpy(key->key, pp, sizeof(key->key));  
  360. pp += sizeof(key->key);  
  361. key->random1_size = 764 - offset - 128 - 4;  
  362. if (key->random1_size > 0) {  
  363. key->random1 = new char[key->random1_size];  
  364. memcpy(key->random1, pp, key->random1_size);  
  365. }  
  366. return ret;  
  367. }  
  368. // free the block data create by  
  369. // srs_key_block_init or srs_key_block_parse  
  370. void srs_key_block_free(key_block* key)  
  371. {  
  372. if (key->random0) {  
  373. srs_freepa(key->random0);  
  374. }  
  375. if (key->random1) {  
  376. srs_freepa(key->random1);  
  377. }  
  378. }  
  379.   
  380. /** 
  381. * 764bytes digest結構 
  382. * offset: 4bytes 
  383. * random-data: (offset)bytes 
  384. * digest-data: 32bytes 
  385. * random-data: (764-4-offset-32)bytes 
  386. */  
  387. struct digest_block  
  388. {  
  389. // 4bytes  
  390. int32_t offset;  
  391. // (offset)bytes  
  392. char* random0;  
  393. int random0_size;  
  394. // 32bytes  
  395. char digest[32];  
  396. // (764-4-offset-32)bytes  
  397. char* random1;  
  398. int random1_size;  
  399. };  
  400. // calc the offset of digest,  
  401. // the key->offset cannot be used as the offset of digest.  
  402. int srs_digest_block_get_offset(digest_block* digest)  
  403. {  
  404. int max_offset_size = 764 - 32 - 4;  
  405. int offset = 0;  
  406. u_int8_t* pp = (u_int8_t*)&digest->offset;  
  407. offset += *pp++;  
  408. offset += *pp++;  
  409. offset += *pp++;  
  410. offset += *pp++;  
  411.   
  412. return offset % max_offset_size;  
  413. }  
  414. // create new digest block data.  
  415. // if created, user must free it by srs_digest_block_free  
  416. void srs_digest_block_init(digest_block* digest)  
  417. {  
  418. digest->offset = (int32_t)rand();  
  419. digest->random0 = NULL;  
  420. digest->random1 = NULL;  
  421. int offset = srs_digest_block_get_offset(digest);  
  422. srs_assert(offset >= 0);  
  423. digest->random0_size = offset;  
  424. if (digest->random0_size > 0) {  
  425. digest->random0 = new char[digest->random0_size];  
  426. for (int i = 0; i < digest->random0_size; i++) {  
  427. *(digest->random0 + i) = rand() % 256;  
  428. }  
  429. }  
  430. for (int i = 0; i < (int)sizeof(digest->digest); i++) {  
  431. *(digest->digest + i) = rand() % 256;  
  432. }  
  433. digest->random1_size = 764 - 4 - offset - 32;  
  434. if (digest->random1_size > 0) {  
  435. digest->random1 = new char[digest->random1_size];  
  436. for (int i = 0; i < digest->random1_size; i++) {  
  437. *(digest->random1 + i) = rand() % 256;  
  438. }  
  439. }  
  440. }  
  441. // parse digest block from c1s1.  
  442. // if created, user must free it by srs_digest_block_free  
  443. // @c1s1_digest_bytes the digest start bytes, maybe c1s1 or c1s1+764  
  444. int srs_digest_block_parse(digest_block* digest, char* c1s1_digest_bytes)  
  445. {  
  446. int ret = ERROR_SUCCESS;  
  447.   
  448. char* pp = c1s1_digest_bytes;  
  449. digest->offset = *(int32_t*)pp;  
  450. pp += sizeof(int32_t);  
  451. digest->random0 = NULL;  
  452. digest->random1 = NULL;  
  453. int offset = srs_digest_block_get_offset(digest);  
  454. srs_assert(offset >= 0);  
  455. digest->random0_size = offset;  
  456. if (digest->random0_size > 0) {  
  457. digest->random0 = new char[digest->random0_size];  
  458. memcpy(digest->random0, pp, digest->random0_size);  
  459. }  
  460. pp += digest->random0_size;  
  461. memcpy(digest->digest, pp, sizeof(digest->digest));  
  462. pp += sizeof(digest->digest);  
  463. digest->random1_size = 764 - 4 - offset - 32;  
  464. if (digest->random1_size > 0) {  
  465. digest->random1 = new char[digest->random1_size];  
  466. memcpy(digest->random1, pp, digest->random1_size);  
  467. }  
  468. return ret;  
  469. }  
  470. // free the block data create by  
  471. // srs_digest_block_init or srs_digest_block_parse  
  472. void srs_digest_block_free(digest_block* digest)  
  473. {  
  474. if (digest->random0) {  
  475. srs_freepa(digest->random0);  
  476. }  
  477. if (digest->random1) {  
  478. srs_freepa(digest->random1);  
  479. }  
  480. }  
  481.   
  482. /** 
  483. * the schema type. 
  484. */  
  485. enum srs_schema_type {  
  486. srs_schema0 = 0, // key-digest sequence  
  487. srs_schema1 = 1, // digest-key sequence  
  488. srs_schema_invalid = 2,  
  489. };  
  490.   
  491. void __srs_time_copy_to(char*& pp, int32_t time)  
  492. {  
  493. // 4bytes time  
  494. *(int32_t*)pp = time;  
  495. pp += 4;  
  496. }  
  497. void __srs_version_copy_to(char*& pp, int32_t version)  
  498. {  
  499. // 4bytes version  
  500. *(int32_t*)pp = version;  
  501. pp += 4;  
  502. }  
  503. void __srs_key_copy_to(char*& pp, key_block* key)  
  504. {  
  505. // 764bytes key block  
  506. if (key->random0_size > 0) {  
  507. memcpy(pp, key->random0, key->random0_size);  
  508. }  
  509. pp += key->random0_size;  
  510. memcpy(pp, key->key, sizeof(key->key));  
  511. pp += sizeof(key->key);  
  512. if (key->random1_size > 0) {  
  513. memcpy(pp, key->random1, key->random1_size);  
  514. }  
  515. pp += key->random1_size;  
  516. *(int32_t*)pp = key->offset;  
  517. pp += 4;  
  518. }  
  519. void __srs_digest_copy_to(char*& pp, digest_block* digest, bool with_digest)  
  520. {  
  521. // 732bytes digest block without the 32bytes digest-data  
  522. // nbytes digest block part1  
  523. *(int32_t*)pp = digest->offset;  
  524. pp += 4;  
  525. if (digest->random0_size > 0) {  
  526. memcpy(pp, digest->random0, digest->random0_size);  
  527. }  
  528. pp += digest->random0_size;  
  529. // digest  
  530. if (with_digest) {  
  531. memcpy(pp, digest->digest, 32);  
  532. pp += 32;  
  533. }  
  534. // nbytes digest block part2  
  535. if (digest->random1_size > 0) {  
  536. memcpy(pp, digest->random1, digest->random1_size);  
  537. }  
  538. pp += digest->random1_size;  
  539. }  
  540.   
  541. /** 
  542. * copy whole c1s1 to bytes. 
  543. */  
  544. void srs_schema0_copy_to(char* bytes, bool with_digest,  
  545. int32_t time, int32_t version, key_block* key, digest_block* digest)  
  546. {  
  547. char* pp = bytes;  
  548.   
  549. __srs_time_copy_to(pp, time);  
  550. __srs_version_copy_to(pp, version);  
  551. __srs_key_copy_to(pp, key);  
  552. __srs_digest_copy_to(pp, digest, with_digest);  
  553. if (with_digest) {  
  554. srs_assert(pp - bytes == 1536);  
  555. else {  
  556. srs_assert(pp - bytes == 1536 - 32);  
  557. }  
  558. }  
  559. void srs_schema1_copy_to(char* bytes, bool with_digest,  
  560. int32_t time, int32_t version, digest_block* digest, key_block* key)  
  561. {  
  562. char* pp = bytes;  
  563.   
  564. __srs_time_copy_to(pp, time);  
  565. __srs_version_copy_to(pp, version);  
  566. __srs_digest_copy_to(pp, digest, with_digest);  
  567. __srs_key_copy_to(pp, key);  
  568. if (with_digest) {  
  569. srs_assert(pp - bytes == 1536);  
  570. else {  
  571. srs_assert(pp - bytes == 1536 - 32);  
  572. }  
  573. }  
  574. /** 
  575. * c1s1 is splited by digest: 
  576. * c1s1-part1: n bytes (time, version, key and digest-part1). 
  577. * digest-data: 32bytes 
  578. * c1s1-part2: (1536-n-32)bytes (digest-part2) 
  579. */  
  580. char* srs_bytes_join_schema0(int32_t time, int32_t version, key_block* key, digest_block* digest)  
  581. {  
  582. char* bytes = new char[1536 -32];  
  583. srs_schema0_copy_to(bytes, false, time, version, key, digest);  
  584. return bytes;  
  585. }  
  586. /** 
  587. * c1s1 is splited by digest: 
  588. * c1s1-part1: n bytes (time, version and digest-part1). 
  589. * digest-data: 32bytes 
  590. * c1s1-part2: (1536-n-32)bytes (digest-part2 and key) 
  591. */  
  592. char* srs_bytes_join_schema1(int32_t time, int32_t version, digest_block* digest, key_block* key)  
  593. {  
  594. char* bytes = new char[1536 -32];  
  595. srs_schema1_copy_to(bytes, false, time, version, digest, key);  
  596. return bytes;  
  597. }  
  598.   
  599. /** 
  600. * compare the memory in bytes. 
  601. */  
  602. bool srs_bytes_equals(void* pa, void* pb, int size){  
  603. u_int8_t* a = (u_int8_t*)pa;  
  604. u_int8_t* b = (u_int8_t*)pb;  
  605. for(int i = 0; i < size; i++){  
  606. if(a[i] != b[i]){  
  607. return false;  
  608. }  
  609. }  
  610.   
  611. return true;  
  612. }  
  613.   
  614. /** 
  615. * c1s1 schema0 
  616. * time: 4bytes 
  617. * version: 4bytes 
  618. * key: 764bytes 
  619. * digest: 764bytes 
  620. * c1s1 schema1 
  621. * time: 4bytes 
  622. * version: 4bytes 
  623. * digest: 764bytes 
  624. * key: 764bytes 
  625. */  
  626. struct c1s1  
  627. {  
  628. union block {  
  629. key_block key;  
  630. digest_block digest;  
  631. };  
  632. // 4bytes  
  633. int32_t time;  
  634. // 4bytes  
  635. int32_t version;  
  636. // 764bytes  
  637. // if schema0, use key  
  638. // if schema1, use digest  
  639. block block0;  
  640. // 764bytes  
  641. // if schema0, use digest  
  642. // if schema1, use key  
  643. block block1;  
  644. // the logic schema  
  645. srs_schema_type schema;  
  646. c1s1();  
  647. virtual ~c1s1();  
  648. /** 
  649. * get the digest key. 
  650. */  
  651. virtual char* get_digest();  
  652. /** 
  653. * copy to bytes. 
  654. */  
  655. virtual void dump(char* _c1s1);  
  656. /** 
  657. * client: create and sign c1 by schema. 
  658. * sign the c1, generate the digest. 
  659. * calc_c1_digest(c1, schema) { 
  660. * get c1s1-joined from c1 by specified schema 
  661. * digest-data = HMACsha256(c1s1-joined, FPKey, 30) 
  662. * return digest-data; 
  663. * } 
  664. * random fill 1536bytes c1 // also fill the c1-128bytes-key 
  665. * time = time() // c1[0-3] 
  666. * version = [0x80, 0x00, 0x07, 0x02] // c1[4-7] 
  667. * schema = choose schema0 or schema1 
  668. * digest-data = calc_c1_digest(c1, schema) 
  669. * copy digest-data to c1 
  670. */  
  671. virtual int c1_create(srs_schema_type _schema);  
  672. /** 
  673. * server: parse the c1s1, discovery the key and digest by schema. 
  674. * use the c1_validate_digest() to valid the digest of c1. 
  675. */  
  676. virtual int c1_parse(char* _c1s1, srs_schema_type _schema);  
  677. /** 
  678. * server: validate the parsed schema and c1s1 
  679. */  
  680. virtual int c1_validate_digest(bool& is_valid);  
  681. /** 
  682. * server: create and sign the s1 from c1. 
  683. */  
  684. virtual int s1_create(c1s1* c1);  
  685. private:  
  686. virtual int calc_s1_digest(char*& digest);  
  687. virtual int calc_c1_digest(char*& digest);  
  688. virtual void destroy_blocks();  
  689. };  
  690.   
  691. /** 
  692. * the c2s2 complex handshake structure. 
  693. * random-data: 1504bytes 
  694. * digest-data: 32bytes 
  695. */  
  696. struct c2s2  
  697. {  
  698. char random[1504];  
  699. char digest[32];  
  700. c2s2();  
  701. virtual ~c2s2();  
  702. /** 
  703. * copy to bytes. 
  704. */  
  705. virtual void dump(char* _c2s2);  
  706.   
  707. /** 
  708. * create c2. 
  709. * random fill c2s2 1536 bytes 
  710. * // client generate C2, or server valid C2 
  711. * temp-key = HMACsha256(s1-digest, FPKey, 62) 
  712. * c2-digest-data = HMACsha256(c2-random-data, temp-key, 32) 
  713. */  
  714. virtual int c2_create(c1s1* s1);  
  715. /** 
  716. * create s2. 
  717. * random fill c2s2 1536 bytes 
  718. * // server generate S2, or client valid S2 
  719. * temp-key = HMACsha256(c1-digest, FMSKey, 68) 
  720. * s2-digest-data = HMACsha256(s2-random-data, temp-key, 32) 
  721. */  
  722. virtual int s2_create(c1s1* c1);  
  723. };  
  724.   
  725. c2s2::c2s2()  
  726. {  
  727. for (int i = 0; i < 1504; i++) {  
  728. *(random + i) = rand();  
  729. }  
  730. for (int i = 0; i < 32; i++) {  
  731. *(digest + i) = rand();  
  732. }  
  733. }  
  734.   
  735. c2s2::~c2s2()  
  736. {  
  737. }  
  738.   
  739. void c2s2::dump(char* _c2s2)  
  740. {  
  741. memcpy(_c2s2, random, 1504);  
  742. memcpy(_c2s2 + 1504, digest, 32);  
  743. }  
  744.   
  745. int c2s2::c2_create(c1s1* s1)  
  746. {  
  747. int ret = ERROR_SUCCESS;  
  748. char temp_key[OpensslHashSize];  
  749. if ((ret = openssl_HMACsha256(s1->get_digest(), 32, SrsGenuineFPKey, 62, temp_key)) != ERROR_SUCCESS) {  
  750. srs_error("create c2 temp key failed. ret=%d", ret);  
  751. return ret;  
  752. }  
  753. srs_verbose("generate c2 temp key success.");  
  754. char _digest[OpensslHashSize];  
  755. if ((ret = openssl_HMACsha256(random, 1504, temp_key, 32, _digest)) != ERROR_SUCCESS) {  
  756. srs_error("create c2 digest failed. ret=%d", ret);  
  757. return ret;  
  758. }  
  759. srs_verbose("generate c2 digest success.");  
  760. memcpy(digest, _digest, 32);  
  761. return ret;  
  762. }  
  763.   
  764. int c2s2::s2_create(c1s1* c1)  
  765. {  
  766. int ret = ERROR_SUCCESS;  
  767. char temp_key[OpensslHashSize];  
  768. if ((ret = openssl_HMACsha256(c1->get_digest(), 32, SrsGenuineFMSKey, 68, temp_key)) != ERROR_SUCCESS) {  
  769. srs_error("create s2 temp key failed. ret=%d", ret);  
  770. return ret;  
  771. }  
  772. srs_verbose("generate s2 temp key success.");  
  773. char _digest[OpensslHashSize];  
  774. if ((ret = openssl_HMACsha256(random, 1504, temp_key, 32, _digest)) != ERROR_SUCCESS) {  
  775. srs_error("create s2 digest failed. ret=%d", ret);  
  776. return ret;  
  777. }  
  778. srs_verbose("generate s2 digest success.");  
  779. memcpy(digest, _digest, 32);  
  780. return ret;  
  781. }  
  782.   
  783. c1s1::c1s1()  
  784. {  
  785. schema = srs_schema_invalid;  
  786. }  
  787. c1s1::~c1s1()  
  788. {  
  789. destroy_blocks();  
  790. }  
  791.   
  792. char* c1s1::get_digest()  
  793. {  
  794. srs_assert(schema != srs_schema_invalid);  
  795. if (schema == srs_schema0) {  
  796. return block1.digest.digest;  
  797. else {  
  798. return block0.digest.digest;  
  799. }  
  800. }  
  801.   
  802. void c1s1::dump(char* _c1s1)  
  803. {  
  804. srs_assert(schema != srs_schema_invalid);  
  805. if (schema == srs_schema0) {  
  806. srs_schema0_copy_to(_c1s1, true, time, version, &block0.key, &block1.digest);  
  807. else {  
  808. srs_schema1_copy_to(_c1s1, true, time, version, &block0.digest, &block1.key);  
  809. }  
  810. }  
  811.   
  812. int c1s1::c1_create(srs_schema_type _schema)  
  813. {  
  814. int ret = ERROR_SUCCESS;  
  815. if (_schema == srs_schema_invalid) {  
  816. ret = ERROR_RTMP_CH_SCHEMA;  
  817. srs_error("create c1 failed. invalid schema=%d, ret=%d", _schema, ret);  
  818. return ret;  
  819. }  
  820. destroy_blocks();  
  821. time = ::time(NULL);  
  822. version = 0x02070080; // client c1 version  
  823. if (_schema == srs_schema0) {  
  824. srs_key_block_init(&block0.key);  
  825. srs_digest_block_init(&block1.digest);  
  826. else {  
  827. srs_digest_block_init(&block0.digest);  
  828. srs_key_block_init(&block1.key);  
  829. }  
  830. schema = _schema;  
  831. char* digest = NULL;  
  832. if ((ret = calc_c1_digest(digest)) != ERROR_SUCCESS) {  
  833. srs_error("sign c1 error, failed to calc digest. ret=%d", ret);  
  834. return ret;  
  835. }  
  836. srs_assert(digest != NULL);  
  837. SrsAutoFree(char, digest, true);  
  838. if (schema == srs_schema0) {  
  839. memcpy(block1.digest.digest, digest, 32);  
  840. else {  
  841. memcpy(block0.digest.digest, digest, 32);  
  842. }  
  843. return ret;  
  844. }  
  845.   
  846. int c1s1::c1_parse(char* _c1s1, srs_schema_type _schema)  
  847. {  
  848. int ret = ERROR_SUCCESS;  
  849. if (_schema == srs_schema_invalid) {  
  850. ret = ERROR_RTMP_CH_SCHEMA;  
  851. srs_error("parse c1 failed. invalid schema=%d, ret=%d", _schema, ret);  
  852. return ret;  
  853. }  
  854. destroy_blocks();  
  855. time = *(int32_t*)_c1s1;  
  856. version = *(int32_t*)(_c1s1 + 4); // client c1 version  
  857. if (_schema == srs_schema0) {  
  858. if ((ret = srs_key_block_parse(&block0.key, _c1s1 + 8)) != ERROR_SUCCESS) {  
  859. srs_error("parse the c1 key failed. ret=%d", ret);  
  860. return ret;  
  861. }  
  862. if ((ret = srs_digest_block_parse(&block1.digest, _c1s1 + 8 + 764)) != ERROR_SUCCESS) {  
  863. srs_error("parse the c1 digest failed. ret=%d", ret);  
  864. return ret;  
  865. }  
  866. srs_verbose("parse c1 key-digest success");  
  867. else if (_schema == srs_schema1) {  
  868. if ((ret = srs_digest_block_parse(&block0.digest, _c1s1 + 8)) != ERROR_SUCCESS) {  
  869. srs_error("parse the c1 key failed. ret=%d", ret);  
  870. return ret;  
  871. }  
  872. if ((ret = srs_key_block_parse(&block1.key, _c1s1 + 8 + 764)) != ERROR_SUCCESS) {  
  873. srs_error("parse the c1 digest failed. ret=%d", ret);  
  874. return ret;  
  875. }  
  876. srs_verbose("parse c1 digest-key success");  
  877. else {  
  878. ret = ERROR_RTMP_CH_SCHEMA;  
  879. srs_error("parse c1 failed. invalid schema=%d, ret=%d", _schema, ret);  
  880. return ret;  
  881. }  
  882. schema = _schema;  
  883. return ret;  
  884. }  
  885.   
  886. int c1s1::c1_validate_digest(bool& is_valid)  
  887. {  
  888. int ret = ERROR_SUCCESS;  
  889. char* c1_digest = NULL;  
  890. if ((ret = calc_c1_digest(c1_digest)) != ERROR_SUCCESS) {  
  891. srs_error("validate c1 error, failed to calc digest. ret=%d", ret);  
  892. return ret;  
  893. }  
  894. srs_assert(c1_digest != NULL);  
  895. SrsAutoFree(char, c1_digest, true);  
  896. if (schema == srs_schema0) {  
  897. is_valid = srs_bytes_equals(block1.digest.digest, c1_digest, 32);  
  898. else {  
  899. is_valid = srs_bytes_equals(block0.digest.digest, c1_digest, 32);  
  900. }  
  901. return ret;  
  902. }  
  903.   
  904. int c1s1::s1_create(c1s1* c1)  
  905. {  
  906. int ret = ERROR_SUCCESS;  
  907. if (c1->schema == srs_schema_invalid) {  
  908. ret = ERROR_RTMP_CH_SCHEMA;  
  909. srs_error("create s1 failed. invalid schema=%d, ret=%d", c1->schema, ret);  
  910. return ret;  
  911. }  
  912. destroy_blocks();  
  913. schema = c1->schema;  
  914. time = ::time(NULL);  
  915. version = 0x01000504; // server s1 version  
  916. if (schema == srs_schema0) {  
  917. srs_key_block_init(&block0.key);  
  918. srs_digest_block_init(&block1.digest);  
  919. else {  
  920. srs_digest_block_init(&block0.digest);  
  921. srs_key_block_init(&block1.key);  
  922. }  
  923. if (schema == srs_schema0) {  
  924. if ((ret = openssl_generate_key(c1->block0.key.key, block0.key.key, 128)) != ERROR_SUCCESS) {  
  925. srs_error("calc s1 key failed. ret=%d", ret);  
  926. return ret;  
  927. }  
  928. else {  
  929. if ((ret = openssl_generate_key(c1->block1.key.key, block1.key.key, 128)) != ERROR_SUCCESS) {  
  930. srs_error("calc s1 key failed. ret=%d", ret);  
  931. return ret;  
  932. }  
  933. }  
  934. srs_verbose("calc s1 key success.");  
  935. char* s1_digest = NULL;  
  936. if ((ret = calc_s1_digest(s1_digest)) != ERROR_SUCCESS) {  
  937. srs_error("calc s1 digest failed. ret=%d", ret);  
  938. return ret;  
  939. }  
  940. srs_verbose("calc s1 digest success.");  
  941. srs_assert(s1_digest != NULL);  
  942. SrsAutoFree(char, s1_digest, true);  
  943. if (schema == srs_schema0) {  
  944. memcpy(block1.digest.digest, s1_digest, 32);  
  945. else {  
  946. memcpy(block0.digest.digest, s1_digest, 32);  
  947. }  
  948. srs_verbose("copy s1 key success.");  
  949. return ret;  
  950. }  
  951.   
  952. int c1s1::calc_s1_digest(char*& digest)  
  953. {  
  954. int ret = ERROR_SUCCESS;  
  955. srs_assert(schema == srs_schema0 || schema == srs_schema1);  
  956. char* c1s1_joined_bytes = NULL;  
  957.   
  958. if (schema == srs_schema0) {  
  959. c1s1_joined_bytes = srs_bytes_join_schema0(time, version, &block0.key, &block1.digest);  
  960. else {  
  961. c1s1_joined_bytes = srs_bytes_join_schema1(time, version, &block0.digest, &block1.key);  
  962. }  
  963. srs_assert(c1s1_joined_bytes != NULL);  
  964. SrsAutoFree(char, c1s1_joined_bytes, true);  
  965. digest = new char[OpensslHashSize];  
  966. if ((ret = openssl_HMACsha256(c1s1_joined_bytes, 1536 - 32, SrsGenuineFMSKey, 36, digest)) != ERROR_SUCCESS) {  
  967. srs_error("calc digest for s1 failed. ret=%d", ret);  
  968. return ret;  
  969. }  
  970. srs_verbose("digest calculated for s1");  
  971. return ret;  
  972. }  
  973.   
  974. int c1s1::calc_c1_digest(char*& digest)  
  975. {  
  976. int ret = ERROR_SUCCESS;  
  977. srs_assert(schema == srs_schema0 || schema == srs_schema1);  
  978. char* c1s1_joined_bytes = NULL;  
  979.   
  980. if (schema == srs_schema0) {  
  981. c1s1_joined_bytes = srs_bytes_join_schema0(time, version, &block0.key, &block1.digest);  
  982. else {  
  983. c1s1_joined_bytes = srs_bytes_join_schema1(time, version, &block0.digest, &block1.key);  
  984. }  
  985. srs_assert(c1s1_joined_bytes != NULL);  
  986. SrsAutoFree(char, c1s1_joined_bytes, true);  
  987. digest = new char[OpensslHashSize];  
  988. if ((ret = openssl_HMACsha256(c1s1_joined_bytes, 1536 - 32, SrsGenuineFPKey, 30, digest)) != ERROR_SUCCESS) {  
  989. srs_error("calc digest for c1 failed. ret=%d", ret);  
  990. return ret;  
  991. }  
  992. srs_verbose("digest calculated for c1");  
  993. return ret;  
  994. }  
  995.   
  996. void c1s1::destroy_blocks()  
  997. {  
  998. if (schema == srs_schema_invalid) {  
  999. return;  
  1000. }  
  1001. if (schema == srs_schema0) {  
  1002. srs_key_block_free(&block0.key);  
  1003. srs_digest_block_free(&block1.digest);  
  1004. else {  
  1005. srs_digest_block_free(&block0.digest);  
  1006. srs_key_block_free(&block1.key);  
  1007. }  
  1008. }  
  1009.   
  1010. SrsComplexHandshake::SrsComplexHandshake()  
  1011. {  
  1012. }  
  1013.   
  1014. SrsComplexHandshake::~SrsComplexHandshake()  
  1015. {  
  1016. }  
  1017.   
  1018. int SrsComplexHandshake::handshake(SrsSocket& skt, char* _c1)  
  1019. {  
  1020. int ret = ERROR_SUCCESS;  
  1021. ssize_t nsize;  
  1022. static bool _random_initialized = false;  
  1023. if (!_random_initialized) {  
  1024. srand(0);  
  1025. _random_initialized = true;  
  1026. srs_trace("srand initialized the random.");  
  1027. }  
  1028. // decode c1  
  1029. c1s1 c1;  
  1030. // try schema0.  
  1031. if ((ret = c1.c1_parse(_c1, srs_schema0)) != ERROR_SUCCESS) {  
  1032. srs_error("parse c1 schema%d error. ret=%d", srs_schema0, ret);  
  1033. return ret;  
  1034. }  
  1035. // try schema1  
  1036. bool is_valid = false;  
  1037. if ((ret = c1.c1_validate_digest(is_valid)) != ERROR_SUCCESS || !is_valid) {  
  1038. if ((ret = c1.c1_parse(_c1, srs_schema1)) != ERROR_SUCCESS) {  
  1039. srs_error("parse c1 schema%d error. ret=%d", srs_schema1, ret);  
  1040. return ret;  
  1041. }  
  1042. if ((ret = c1.c1_validate_digest(is_valid)) != ERROR_SUCCESS || !is_valid) {  
  1043. ret = ERROR_RTMP_TRY_SIMPLE_HS;  
  1044. srs_info("all schema valid failed, try simple handshake. ret=%d", ret);  
  1045. return ret;  
  1046. }  
  1047. }  
  1048. srs_verbose("decode c1 success.");  
  1049. // encode s1  
  1050. c1s1 s1;  
  1051. if ((ret = s1.s1_create(&c1)) != ERROR_SUCCESS) {  
  1052. srs_error("create s1 from c1 failed. ret=%d", ret);  
  1053. return ret;  
  1054. }  
  1055. srs_verbose("create s1 from c1 success.");  
  1056. c2s2 s2;  
  1057. if ((ret = s2.s2_create(&c1)) != ERROR_SUCCESS) {  
  1058. srs_error("create s2 from c1 failed. ret=%d", ret);  
  1059. return ret;  
  1060. }  
  1061. srs_verbose("create s2 from c1 success.");  
  1062. // sendout s0s1s2  
  1063. char* s0s1s2 = new char[3073];  
  1064. SrsAutoFree(char, s0s1s2, true);  
  1065. // plain text required.  
  1066. s0s1s2[0] = 0x03;  
  1067. s1.dump(s0s1s2 + 1);  
  1068. s2.dump(s0s1s2 + 1537);  
  1069. if ((ret = skt.write(s0s1s2, 3073, &nsize)) != ERROR_SUCCESS) {  
  1070. srs_warn("complex handshake send s0s1s2 failed. ret=%d", ret);  
  1071. return ret;  
  1072. }  
  1073. srs_verbose("complex handshake send s0s1s2 success.");  
  1074. // recv c2  
  1075. char* c2 = new char[1536];  
  1076. SrsAutoFree(char, c2, true);  
  1077. if ((ret = skt.read_fully(c2, 1536, &nsize)) != ERROR_SUCCESS) {  
  1078. srs_warn("complex handshake read c2 failed. ret=%d", ret);  
  1079. return ret;  
  1080. }  
  1081. srs_verbose("complex handshake read c2 success.");  
  1082. return ret;  
  1083. }  


 

5.4. 分塊

        握手之後,連接開始對一個或多個塊流進行合併。創建的每個塊都有一個唯一 ID 對其進行關聯,這個 ID 叫做 chunk stream ID (塊流 ID),即csid。這些塊通過網絡進行傳輸。傳遞時,每個塊必須被完全發送纔可以發送下一塊。在接收端,這些塊被根據塊流 ID 被組裝成消息。
        分塊允許上層協議將大的消息分解爲更小的消息,例如,防止體積大的但優先級小的消息 (比如視頻) 阻礙體積較小但優先級高的消息 (比如音頻或者控制命令)。
        分塊也讓我們能夠使用較小開銷發送小消息,因爲塊頭包含包含在消息內部的信息壓縮提示。
        塊的大小是可以配置的。它可以使用一個設置塊大小的控制消息進行設置 (參考 5.4.1)。更大的塊大小可以降低 CPU 開銷,但在低帶寬連接時因爲它的大量的寫入也會延遲其他內容的傳遞。更小的塊不利於高比特率的流化。所以塊的大小設置取決於具體情況。

        5.4.1. 塊格式

        每個塊包含一個頭和數據體。塊頭包含三個部分:
Chunk Format
        Basic Header (基本頭,1 到 3 個字節)這個字段對塊流 ID 和塊類型進行編碼。塊類型決定了消息頭的編碼格式。(這一字段的) 長度完全取決於塊流 ID,因爲塊流 ID 是一個可變長度的字段。
        Message Header (消息頭,0,3,7,或者 11 個字節):這一字段對正在發送的消息 (不管是整個消息,還是隻是一小部分) 的信息進行編碼。這一字段的長度可以使用塊頭中定義的塊類型進行決定。
        Extended Timestamp (擴展 timestamp,0 或 4 字節):這一字段是否出現取決於塊消息頭中的 timestamp 或者 timestamp delta 字段。更多信息參考 5.4.1.3 節。

        Chunk Data (可變的大小):當前塊的有效負載,大小由配置決定。

        5.4.1.1. 塊基本頭
        塊基本頭對塊流 ID (chunk stream id=csid)和塊類型 (由下圖中的 fmt 字段表示) 進行編碼。塊基本頭字段可能會有 1,2 或者 3 個字節,取決於塊流 ID
        一個 (RTMP) 實現應該使用能夠容納這個 ID 的最小的容量進行表示。
        RTMP 協議最多支持 65597 個流流 ID 範圍 3 - 65599。ID 0、1、2 被保留。0 值表示二字節形式,並且 ID 範圍 64 - 319 (第二個字節 + 64)。1 值表示三字節形式,並且 ID 範圍爲 64 - 65599 ((第三個字節) * 256 + 第二個字節 + 64)。3 - 63 範圍內的值表示整個流 ID。帶有 2 值的塊流 ID 被保留,用於下層協議控制消息和命令。
        塊基本頭中的 0 - 5 位 (最低有效) 代表塊流 ID。
        塊流 ID 2 - 63 可以編進這一字段的一字節版本中。
Chunk basic header 1
        塊流 ID 64 - 319 可以以二字節的形式編碼在頭中。ID 計算爲 (第二個字節 + 64),這個時候第一字節的fmt後面的6個bit爲000000
Chunk basic header 2
        塊流 ID 64 - 65599 可以編碼在這個字段的三字節版本中。ID 計算爲 ((第三個字節) * 256 + (第二個字節) + 64),這個時候第一字節的fmt後面的6個bit爲000001
Chunk basic header 3

       例如:如果csid=288=224+64,那麼應該表示成fmt00000011100000(2-byte)或者fmt0000010000000011100000(3-byte)。

        csid (六位):這一字段包含有塊流 ID,值的範圍是 2 - 63。fmt後面緊跟着的6位的值, 0 和 1 用於指示這一字段是 2- 或者 3- 字節版本

        fmt (兩個字節):這一字段指示 'chunk message header' 使用的四種格式之一。每種塊類型的 'chunk message header' 會在下一小節解釋。
        csid - 64 (8 或者 16 位):這一字段包含了塊流 ID 減掉 64 後的值塊流 ID,即csid - 64爲塊流 ID在字節中的表現形式
        塊流 ID 64 - 319 可以使用 2-byte 或者 3-byte 的形式在頭中表示,塊流 ID320-65599只能使用3-byte的形式表示。
        5.4.1.2. 塊消息頭(fmt的值)
        塊消息頭又四種不同的格式,由塊基本頭中的 "fmt" 字段進行選擇。
        一個 (RTMP) 實現應該爲每個塊消息頭使用最緊湊的表示。
            5.4.1.2.1. 類型 0

        類型 0 塊頭的長度是 11 個字節。即AMF0包頭爲11字節。這一類型必須用在塊流的起始位置,和流 timestamp 重來的時候 (比如,重置)

 

 

圖例1:符合AMF0類型的協議控制包(play&set buffer length)

        例如圖例1,是一個fmt=0的rtmp的協議控制包(play&set buffer length),csid=8。圖中選中字段爲包含消息流ID一字節的AMF0包頭(12字節,去掉第一個字節的0x08,即爲AMF0的11字節包頭)。其中timestamp(3個字節,00 01 8d) 爲397,message length(body size字段,3個字節,00 00 18)爲24.type id(stream id,1個字節)代表body的幀類型爲0x11(AMF3 command類型,1字節 0x11),代表是指令幀。msg stream id爲1(四個字節,01 00 00 00).

 

Chunk Message Header - Type 0
        timestamp (三個字節):對於 type-0 塊,當前消息的絕對 timestamp 在這裏發送。如果 timestamp 大於或者等於 16777215 (十六進制 0xFFFFFF),這一字段必須是 16777215,表明有擴展 timestamp 字段來補充完整的 32 位 timestamp。否則的話,這一字段必須是整個的 timestamp。
        5.4.1.2.2. 類型 1

        類型 1 塊頭長爲 7 個字節即AMF1包頭爲7字節。不包含消息流 ID;這一塊使用前一塊一樣的流 ID。可變長度消息的流 (例如,一些視頻格式) 應該在第一塊之後使用這一格式表示之後的每個新消息。

圖例2:符合AMF1類型的視頻包

       例如圖例2,是一個fmt=1的rtmp的視頻包。csid=4,圖中選中字段爲包含消息流ID一字節的AMF1包頭(8字節,去掉第一個字節的0x44,即爲AMF1的包頭)。其中timestamp delta爲6,message length(body size字段)爲628.type id代表body的幀類型爲9,是視頻幀。

Chunk Message Header - Type 1
        5.4.1.2.3. 類型 2

        類型 2 塊頭長度爲 3 個字節即AMF2包頭爲3字節。既不包含流 ID 也不包含消息長度;這一塊具有和前一塊相同的流 ID 和消息長度。具有不變長度的消息 (例如,一些音頻和數據格式) 應該在第一塊之後使用這一格式表示之後的每個新消息。

 

圖例3:符合AMF2類型的包

        例如圖例3,是一個fmt=2的rtmp的固定長度的數據包。csid=37,圖中選中字段爲不包含消息流ID 1字節的AMF2包頭(3字節,消息流ID爲0xa5)。其中timestamp delta爲4357388,無message length字段,因爲默認與前以對應csid包的數據大小相同。沒有幀類型,緊跟數據。

Chunk Message Header - Type 2
        5.4.1.2.4. 類型 3
        類型 3 的塊沒有消息頭。流 ID、消息長度以及 timestamp delta 等字段都不存在;這種類型的塊使用前面塊一樣的塊流 ID。即AMF3無包頭當單一一個消息被分割爲多塊時,除了第一塊的其他塊都應該使用這種類型。參考例 2 (5.3.2.2 小節)。組成流的消息具有同樣的大小,流 ID 和時間間隔應該在類型 2 之後的所有塊都使用這一類型。參考例 1 (5.3.2.1 小節)。如果第一個消息和第二個消息之間的 delta 和第一個消息的 timestamp 一樣的話,那麼在類型 0 的塊之後要緊跟一個類型 3 的塊,因爲無需再來一個類型 2 的塊來註冊 delta 了。如果一個類型 3 的塊跟着一個類型 0 的塊,那麼這個類型 3 塊的 timestamp delta 和類型 0 塊的 timestamp 是一樣的。
        5.4.1.2.5. 通用頭字段
        塊消息頭中各字段的描述如下:
        timestamp delta (三個字節):對於一個類型 1 或者類型 2 的塊,前一塊的 timestamp 和當前塊的 timestamp 的區別在這裏發送。如果 delta 大於或者等於 16777215 (十六進制 0xFFFFFF),那麼這一字段必須是爲 16777215,表示具有擴展 timestamp 字段來對整個 32 位 delta 進行編碼。否則的話,這一字段應該是爲具體 delta。
        message length (三個字節):對於一個類型 0 或者類型 1 的塊,消息長度在這裏進行發送。注意這通常不同於塊的有效載荷的長度。塊的有效載荷代表所有的除了最後一塊的最大塊大小,以及剩餘的 (也可能是小消息的整個長度) 最後一塊。
        message type id (消息類型 id,一個字節):對於類型 0 或者類型 1 的塊,消息的類型在這裏發送。
        message stream id (四個字節):對於一個類型爲 0 的塊,保存消息流 ID。消息流 ID 以小端格式保存。所有同一個塊流下的消息都來自同一個消息流。當可以將不同的消息流組合進同一個塊流時,這種方法比頭壓縮的做法要好。但是,當一個消息流被關閉而其他的隨後另一個是打開着的,就沒有理由將現有塊流以發送一個新的類型 0 的塊進行復用了。
        5.4.1.3. 擴展 timestamp
        擴展 timestamp 字段用於對大於 16777215 (0xFFFFFF) 的 timestamp 或者 timestamp delta 進行編碼;也就是,對於不適合於在 24 位的類型 0、1 和 2 的塊裏的 timestamp 和 timestamp delta 編碼。這一字段包含了整個 32 位的 timestamp 或者 timestamp delta 編碼。可以通過設置類型 0 塊的 timestamp 字段、類型 1 或者 2 塊的 timestamp delta 字段 16777215 (0xFFFFFF) 來啓用這一字段。當最近的具有同一塊流的類型 0、1 或 2 塊指示擴展 timestamp 字段出現時,這一字段纔會在類型爲 3 的塊中出現。

        5.4.2. 例子

        5.4.2.1. 例子 1
        這個例子演示了一個簡單地音頻消息流。這個例子演示了信息的冗餘。
Sample audio messages to be made into chunks
        下一個表格演示了這個流所產生的塊。從消息 3 起,數據傳輸得到了最佳化利用。每條消息的開銷在這一點之後都只有一個字節。
Format of each of the chunks of audio messages
        5.4.2.2. 例子 2
        這一例子闡述了一條消息太大,無法裝在一個 128 字節的塊裏,被分割爲若干塊。
Sample Message to be broken to chunks
        這是傳輸的塊:
Format of each of the chunks
        塊 1 的數據頭說明了整個消息長度是爲 307 個字節。
        由以上倆例子可以得知,塊類型 3 可以被用於兩種不同的方式。第一種是用於定義一條消息的配置。第二種是定義一個可以從現有狀態數據中派生出來的新消息的起點。

        

5.5. 協議控制消息

        RTMP 塊流使用消息類型 ID(stream id) 爲 1、2、3、5 和 6 用於協議控制消息。這些消息包含有 RTMP 塊流協議所需要的信息。
        這些協議控制消息必須使用消息流 ID 0 (作爲已知控制流) 並以流 ID 爲 2 的塊發送。協議控制消息一旦被接收到就立即生效;協議控制消息的 timestamp 被忽略。

        5.5.1. 設置塊類型 (1=set chunk size)

        協議控制消息 1,設置塊大小,以通知對端一個新的最大塊大小(set chunk size)。
        默認的最大塊大小是爲 128 字節,但是客戶端或者服務器可以改變這個大小,並使用這一消息對對端進行更新。例如,假定一個客戶端想要發送一個 131 字節的音頻數據,當前塊大小是默認的 128。在這種情況下,客戶端可以發送這種消息到服務器以通知它塊大小現在是 131 字節了。這樣客戶端就可以在單一塊中發送整個音頻數據了。
        最大塊大小設置的話最少爲 128 字節,包含內容最少要一個字節。最大塊大小由每個方面 (服務器或者客戶端) 自行維護。
Payload for the ‘Set Chunk Size’ protocol message
        0:這個位必須爲 0。
        chunk size (塊大小,31 位):這一字段保存新的最大塊大小值,以字節爲單位,這將用於之後發送者發送的塊,直到有更多 (關於最大塊大小的) 通知。有效值爲 1 到 2147483647 (0x7FFFFFFF,1 和 2147483647 都可取); 但是所有大於 16777215 (0xFFFFFF) 的大小值是等價的,因爲沒有一個塊比一整個消息大,並且沒有一個消息大於 16777215 字節。

        5.5.2. 終止消息(2,)

        協議控制消息 2,終止消息,用於通知對端,如果對端在等待去完成一個消息的塊的話,然後拋棄一個塊流中已接受到的部分消息。對端接收到塊流 ID 作爲當前協議消息的有效負載。一些程序可能會在關閉的時候使用這個消息以指示不需要進一步對這個消息的處理了。
Payload for the ‘Abort Message’ protocol message
        chunk stream ID (塊流 ID,32 位):這一字段保存塊流 ID,該流的當前消息會被丟棄。

        5.5.3. 確認 (3=acknowledgement)

        客戶端或者服務器在接收到等同於窗口大小的字節之後必須要發送給對端一個確認。窗口大小是指發送者在沒有收到接收者確認之前發送的最大數量的字節。這個消息定義了序列號,也就是目前接收到的字節數。
Payload for the ‘Acknowledgement’ protocol message
        sequence number (序列號,32 位):這一字段保存有目前接收到的字節數。

        5.5.4. 窗口確認大小 (5=window acknowledgement size)

        客戶端或者服務器端發送這條消息來通知對端發送和應答之間的窗口大小。發送者在發送完窗口大小字節之後期待對端的確認。接收端在上次確認發送後接收到的指示數值後,或者會話建立之後尚未發送確認,必須發送一個確認 (5.4.3 小節)。
Payload for the ‘Window Acknowledgement Size’ protocol message

        5.5.5. 設置對端帶寬 (6=set peer bandwidth)

        客戶端或者服務器端發送這一消息來限制其對端的輸出帶寬。對端接收到這一消息後,將通過限制這一消息中窗口大小指出的已發送但未被答覆的數據的數量以限制其輸出帶寬。如果這個窗口大小不同於其發送給 (設置對端帶寬) 發送者的最後一條消息,那麼接收到這一消息的對端應該回復一個窗口確認大小消息。
Payload for the ‘Set Peer Bandwidth’ protocol message
        限制類型取以下值之一:
        0 - Hard:對端應該限制其輸出帶寬到指示的窗口大小。
        1 - Soft:對端應該限制其輸出帶寬到指示的窗口大小,或者已經有限制在其作用的話就取兩者之間的較小值。
        2 - Dynamic:如果先前的限制類型爲 Hard,處理這個消息就好像它被標記爲 Hard,否則的話忽略這個消息。

        

6. RTMP 消息格式

        這一節定義了使用下層傳輸層 (比如 RTMP 塊流協議) 傳輸的 RTMP 消息的格式。
        RTMP 協議設計使用 RTMP 塊流,可以使用其他任意傳輸協議對消息進行發送。RTMP 塊流和 RTMP 一起適用於多種音頻 - 視頻應用,從一對一和一對多直播到點播服務,再到互動會議應用。

       

6.1. RTMP 消息格式

        服務器端和客戶端通過網絡發送 RTMP 消息來進行彼此通信。消息可以包含音頻、視頻、數據,或者其他消息。
        RTMP 消息有兩部分:頭和它的有效載荷。

        6.1.1. 消息頭

        消息頭包含以下:
        Message Type (消息類型):一個字節的字段來表示消息類型。類型 ID 1 - 6 被保留用於協議控制消息。
        Length (長度):三個字節的字段來表示有效負載的字節數。以大端格式保存。
        Timestamp:四個字節的字段包含了當前消息的 timestamp。四個字節也以大端格式保存。
        Message Stream Id (消息流 ID):三個字節的字段以指示出當前消息的流。這三個字節以大端格式保存。
Message Header

        6.1.2. 消息有效載荷

        消息的另一個部分就是有效負載,這是這個消息所包含的實際內容。例如,它可以是一些音頻樣本或者壓縮的視頻數據。有效載荷格式和解釋不在本文檔範圍之內。

        

6.2. 用戶控制消息 (4=user control message)

        RTMP 使用消息類型 ID(message type ID) 4 表示用戶控制消息。這些消息包含 RTMP 流傳輸層所使用的信息。RTMP 塊流協議使用 ID 爲 1、2、3、5 和 6 (5.4 節介紹)
        用戶控制消息應該使用消息流 ID(message stream ID) 0 (以被認爲是控制流),並且以 RTMP 塊流發送時以塊流 ID(chunk stream id) 爲 2。用戶控制消息一旦被接收立馬生效;它們的 timestamp 是被忽略的。
        客戶端或者服務器端發送這個消息來通知對端用戶操作事件。這一消息攜帶有事件類型和事件數據。
Payload for the ‘User Control’ protocol message
        消息數據的前兩個字節用於指示事件類型。事件類型被事件數據緊隨。事件數據字段的大小是可變的。但是,如果消息必須通過 RTMP 塊流層傳輸時,最大塊大小 (5.4.1 節) 應該足夠大以允許這些消息填充在一個單一塊中。
        事件類型和事件數據格式將在 7.1.7 小節列出。

        

7. RTMP 命令消息

        這一節描述了在服務器端和客戶端彼此通信交換的消息和命令的不同的類型。
        服務器端和客戶端交換的不同消息類型包括用於發送音頻數據的音頻消息、用於發送視頻數據的視頻消息、用於發送任意用戶數據的數據消息、共享對象消息以及命令消息。共享對象消息提供了一個通用的方法來管理多用戶和一臺服務器之間的分佈式數據。命令消息在客戶端和服務器端傳輸 AMF 編碼的命令。客戶端或者服務器端可以通過使用命令消息和對端通信的流請求遠程方法調用 (RPC)。
        

7.1. 消息的類型

        服務器端和客戶端通過在網絡中發送消息來進行彼此通信。消息可以是任何類型,包含音頻消息,視頻消息,命令消息,共享對象消息,數據消息,以及用戶控制消息。

        7.1.1. 命令消息 (20, 17)

        命令消息(類似play、connect、closestream)在客戶端和服務器端傳遞 AMF 編碼的命令。這些消息被分配以消息類型值爲 20 以進行 AMF0(AMF0 command 0x14) 編碼,消息類型值爲 17(AMF3 command 0x11)以進行 AMF3 編碼。這些消息發送以進行一些操作,比如,連接,創建流,發佈,播放,對端暫停。命令消息,像 onstatus、result 等等,用於通知發送者請求的命令的狀態。一個命令消息由命令名、事務 ID 和包含相關參數的命令對象組成。一個客戶端或者一個服務器端可以通過和對端通信的流使用這些命令消息請求遠程調用 (RPC)。

        7.1.2. 數據消息 (18, 15)

        客戶端或者服務器端通過發送這些消息以發送元數據或者任何用戶數據到對端。元數據包括數據 (音頻,視頻等等) 的詳細信息,比如創建時間,時長,主題等等。這些消息被分配以消息類型爲 18 以進行 AMF0 編碼和消息類型 15 以進行 AMF3 編碼。

        7.1.3. 共享對象消息 (19, 16)

        所謂共享對象其實是一個 Flash 對象 (一個名值對的集合),這個對象在多個不同客戶端、應用實例中保持同步。消息類型 19 用於 AMF0 編碼、16 用於 AMF3 編碼都被爲共享對象事件保留。每個消息可以包含有不同事件。
The shared object message format
        支持以下事件類型:
事件描述
Use(=1)客戶端發送這一事件以通知服務器端一個已命名的共享對象已創建。
Release(=2)當共享對象在客戶端被刪除時客戶端發送這一事件到服務器端。
Request Change (=3)客戶端發送給服務器端這一事件以請求共享對象的已命名的參數所關聯到的值的改變。
Change (=4)服務器端發送這一事件已通知發起這一請求之外的所有客戶端,一個已命名參數的值的改變。
Success (=5)如果請求被接受,服務器端發送這一事件給請求的客戶端,以作爲 RequestChange 事件的響應。
SendMessage (=6)客戶端發送這一事件到服務器端以廣播一條消息。一旦接收到這一事件,服務器端將會給所有的客戶端廣播這一消息,包括這一消息的發起者。
Status (=7)服務器端發送這一事件以通知客戶端異常情況。
Clear (=8)服務器端發送這一消息到客戶端以清理一個共享對象。服務器端也會對客戶端發送的 Use 事件使用這一事件進行響應。
Remove (=9)服務器端發送這一事件有客戶端刪除一個 slot。
Request Remove (=10)客戶端發送這一事件有客戶端刪除一個 slot。
Use Success (=11)服務器端發送給客戶端這一事件表示連接成功。

        7.1.4. 音頻消息 (8=audio data)

        客戶端或者服務器端發送這一消息以發送音頻數據到對端。消息類型 8 爲音頻消息保留。

        7.1.5. 視頻消息 (9=video data)

        客戶端或者服務器發送這一消息以發送視頻數據到對端。消息類型 9 爲視頻消息保留。

        7.1.6. 統計消息 (22)

        統計消息是一個單一的包含一系列的使用 6.1 節描述的 RTMP 子消息的消息。消息類型 22 用於統計消息。其實就是0x16大包消息。
The Aggregate Message format


The Aggregate Message body format
        統計消息的消息流 ID 覆蓋了統計中子消息的消息流 ID。
        統計消息裏的 timestamp 和第一個子消息的 timestamp 的不同點在於子消息的 timestamp 被相對流時間標調整了偏移。每個子消息的 timestamp 被加入偏移以達到一個統一流時間。第一個子消息的 timestamp 應該和統計消息的 timestamp 一樣,所以這個偏移量應該爲 0。
        反向指針包含有前一個消息的大小 (包含前一個消息的頭)。這樣子匹配了 FLV 文件的格式,用於反向查找。
        使用統計消息具有以下性能優勢:
  • 塊流可以在一個塊中以至多一個單一完整的消息發送。因此,增加塊大小並使用統計消息減少了發送塊的數量。
  • 子消息可以在內存中連續存儲。在網絡中系統調用發送這些數據時更高效。

        7.1.7. 用戶控制消息事件

        客戶端或者服務器端發送這一消息來通知對端用戶控制事件。關於這個的消息格式參考 6.2 節。
        支持以下用戶控制事件類型:
事件描述
Stream Begin (=0)服務器發送這個事件來通知客戶端一個流已就緒並可以用來通信。默認情況下,這一事件在成功接收到客戶端的應用連接命令之後以 ID 0 發送。這一事件數據爲 4 字節,代表了已就緒流的流 ID。
Stream EOF (=1)服務器端發送這一事件來通知客戶端請求的流的回放數據已經結束。在發送額外的命令之前不再發送任何數據。客戶端將丟棄接收到的這個流的消息。這一事件數據爲 4 字節,代表了回放已結束的流的流 ID。
StreamDry (=2)服務器端發送這一事件來通知客戶端當前流中已沒有數據。當服務器端在一段時間內沒有檢測到任何消息,它可以通知相關客戶端當前流已經沒數據了。這一事件數據爲 4 字節,代表了已沒數據的流的流 ID。
SetBuffer Length (=3)客戶端發送這一事件來通知服務器端用於緩存流中任何數據的緩存大小 (以毫秒爲單位)。這一事件在服務器端開始處理流之前就發送。這一事件數據的前 4 個字節代表了流 ID 後 4 個字節代表了以毫秒爲單位的緩存的長度。
StreamIs Recorded (=4)服務器端發送這一事件來通知客戶端當前流是一個錄製流。這一事件數據爲 4 字節,代表了錄製流的流 ID。
PingRequest (=6)服務器端發送這一事件用於測試是否能夠送達客戶端。時間數據是爲一個 4 字節的 timestamp,代表了服務器端發送這一命令時的服務器本地時間。客戶端在接收到這一消息後會立即發送 PingResponse 回覆。
PingResponse (=7)客戶端作爲對 ping 請求的回覆發送這一事件到服務器端。這一事件數據是爲一個 4 字節的 timestamp,就是接收自 PingRequest 那個。

        

7.2. 命令類型

        客戶端和服務器端交換 AMF 編碼的命令。服務器端發送一個命令消息,這個命令消息由命令名、事務 ID 以及包含有相關參數的命令對象組成。例如,包含有 'app' 參數的連接命令,這個命令說明了客戶端連接到的服務器端的應用名。接收者處理這一命令並回發一個同樣事務 ID 的響應。回覆字符串可以是 _result、_error 或者一個方法名的任意一個,比如,verifyClient 或者 contactExternalServer。
        命令字符串 _result 或者 _error 是響應信號。事務 ID 指示出響應所指向的命令。這和 AMAP 和其他一些協議的標籤一樣。命令字符串中的方法名錶示發送者試圖執行接收者一端的一個方法。
        以下類的對象用於發送不同的命令:
        NetConnection 代表上層的服務器端和客戶端之間連接的一個對象。
        NetStream 一個代表發送音頻流、視頻流和其他相關數據的通道的對象。當然,我們也會發送控制數據流的命令,諸如 play、pause 等等。

        7.2.1. NetConnection 命令

        NetConnection 管理着一個客戶端應用和服務器端之間的雙相連接。此外,它還提供遠程方法的異步調用。
        NetConnection 可以發送以下命令:
  • connect
  • call
  • close
  • createStream
        7.2.1.1. connect 命令
        客戶端發送 connect 命令到服務器端來請求連接到一個服務器應用的實例。
        由客戶端發送到服務器端的 connect 命令結構如下:
字段名類型描述
Command Name字符串命令的名字。設置給 "connect"。
Transaction ID數字總是設置爲 1。
Command Object對象具有名值對的命令信息對象。
Optional User Arguments對象任意可選信息。

        以下是爲 connect 命令中使用的名值對對象的描述。
屬性類型描述範例
app字符串客戶端連接到的服務器端應用的名字。testapp
flashver字符串Flash Player 版本號。和ApplicationScript getversion() 方法返回的是同一個字符串。FMSc/1.0
swfUrl字符串進行當前連接的 SWF 文件源地址。例如:app/viewplayer.swffile://C:/FlvPlayer.swf
tcUrl字符串服務器 URL。具有以下格式:protocol://servername:port/appName/appInstance,例如rtmp:IP:PORT/live/rtmp://localhost:1935/testapp/instance1
fpad布爾如果使用了代理就是 true。true 或者 false。
audioCodecs數字表明客戶端所支持的音頻編碼。SUPPORT_SND_MP3
videoCodecs數字表明支持的視頻編碼。SUPPORT_VID_SORENSON
videoFunction數字表明所支持的特殊視頻方法。SUPPORT_VID_CLIENT_SEEK
pageUrl字符串SWF 文件所加載的網頁 URL。可以是undefinedhttp://somehost/sample.html
objectEncoding數字AMF 編碼方法。0、3.一般採用3AMF3


audioCodecs 屬性的標識值:
Flag values for the audioCodecs property

videoCodecs 屬性的標識值:
Flag values for the videoCodecs Property

videoFunction 屬性的標識值:
Flag values for the videoFunction property

encoding 屬性值:
Values for the object encoding property

服務器端到客戶端的命令的結構如下:
The command structure from server to client

Message flow in the connect command

        命令執行時消息流動如下:
                1. 客戶端發送 connect 命令到服務器端以請求對服務器端應用實例的連接。
                2. 收到 connect 命令後,服務器端發送協議消息 '窗口確認大小' 到客戶端。服務器端也會連接到 connect 命令中提到的應用。
                3. 服務器端發送協議消息 '設置對端帶寬' 到客戶端。
                4. 在處理完協議消息 '設置對端帶寬' 之後客戶端發送協議消息 '窗口確認大小' 到服務器端。
                5. 服務器端發送另一個用戶控制消息 (StreamBegin) 類型的協議消息到客戶端。
                6. 服務器端發送結果命令消息告知客戶端連接狀態 (success/fail)。這一命令定義了事務 ID (常常爲 connect 命令設置爲 1)。這一消息也定義了一些屬性,比如 FMS 服務器版本 (字符串)。此外,它還定義了其他連接關聯到的信息,比如 level (字符串)、code (字符串)、description (字符串)、objectencoding (數字) 等等。
        7.2.1.2. call 方法
        NetConnection 對象的 call 方法執行接收端遠程方法的調用 (PRC)。被調用的 PRC 名字作爲一個參數傳給調用命令。
        發送端發送給接收端的命令結構如下:
字段名類型描述
Procedure Name字符串調用的遠程方法的名字。
Transaction ID數字如果期望回覆我們要給一個事務 ID。否則我們傳 0 值即可。
Command Object對象如果存在一些命令信息要設置這個對象,否則置空。
Optional Arguments對象任意要提供的可選參數。


        回覆的命令結構如下:
字段名類型描述
Command Name字符串命令的名字。
Transaction ID數字響應所屬的命令的 ID。
Command Object對象如果存在一些命令信息要設置這個對象,否則置空。
Response對象調用方法的回覆。


        7.2.1.3. createStream 命令

        客戶端發送這一命令到服務器端以爲消息連接創建一個邏輯通道。音頻、視頻和元數據使用 createStream 命令創建的流通道傳輸。
NetConnection 是默認的通信通道,流 ID 爲 0。協議和一些命令消息,包括 createStream,使用默認的通信通道。
        客戶端發送給服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名。設置給 "createStream"。
Transaction ID數字命令的事務 ID。
Command Object對象如果存在一些命令信息要設置這個對象,否則置空。


        服務器端發送給客戶端的命令結構如下:
字段名類型描述
Command Name字符串_result 或者 _error;表明回覆是一個結果還是錯誤。
Transaction ID數字響應所屬的命令的 ID。
Command Object對象如果存在一些命令信息要設置這個對象,否則置空。
Stream ID數字返回值要麼是一個流 ID 要麼是一個錯誤信息對象。


        7.2.2. NetStream 命令

        NetStream 定義了傳輸通道,通過這個通道,音頻流、視頻流以及數據消息流可以通過連接客戶端到服務端的 NetConnection 傳輸。
        以下命令可以由客戶端使用 NetStream 往服務器端發送:
  • play
  • play2
  • deleteStream
  • closeStream
  • receiveAudio
  • receiveVideo
  • publish
  • seek
  • pause

        服務器端使用 "onStatus" 命令向客戶端發送 NetStream 狀態:

字段名類型描述
Command Name字符串命令名 "onStatus"。
Transaction ID數字事務 ID 設置爲 0。
Command ObjectNullonStatus 消息沒有命令對象。
Info Object對象一個 AMF 對象至少要有以下三個屬性。"level" (字符串):這一消息的等級,"warning"、"status"、"error" 中的某個值;"code" (字符串):消息碼,例如 "NetStream.Play.Start";"description" (字符串):關於這個消息人類可讀描述。

        7.2.2.1. play 命令
        客戶端發送這一命令到服務器端以播放流。也可以多次使用這一命令以創建一個播放列表
        如果你想要創建一個動態的播放列表這一可以在不同的直播流或者錄製流之間進行切換播放的話,多次調用 play 方法,並在每次調用時設置Reset的值爲 false。相反的,如果你想要立即播放指定流,需要將其他等待播放的流清空,併爲將Reset設爲 true。
        客戶端發送到服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名。設爲 "play"。
Transaction ID數字事務 ID 設爲 0。
Command ObjectNull命令信息不存在。設爲 null 類型。
Stream Name字符串要播放流的名字。要播放視頻 (FLV) 文件,使用沒有文件擴展名的名字對流名進行定義 (例如,"sample")。要重播 MP3 或者 ID3,你必須在流名前加上 mp3:例如,"mp3:sample"。要播放 H.264/AAC 文件,你必須在流名前加上 mp4:並指定文件擴展名。例如,要播放 sample.m4v 文件,定義 "mp4:sample.m4v"。
Start數字一個可選的參數,以秒爲單位定義開始時間。默認值爲 -2,表示用戶首先嚐試播放流名字段中定義的直播流。如果那個名字的直播流沒有找到,它將播放同名的錄製流。如果沒有那個名字的錄製流,客戶端將等待一個新的那個名字的直播流,並當其有效時進行播放。如果你在 Start 字段中傳遞 -1,那麼就只播放流名中定義的那個名字的直播流。如果你在 Start 字段中傳遞 0 或一個整數,那麼將從 Start 字段定義的時間開始播放流名中定義的那個錄製流。如果沒有找到錄製流,那麼將播放播放列表中的下一項。
Duration數字一個可選的參數,以秒爲單位定義了回放的持續時間。默認值爲 -1。-1 值意味着一個直播流會一直播放直到它不再可用或者一個錄製流一直播放直到結束。如果你傳遞 0 值,它將只播放單一一幀,因爲播放時間已經在錄製流的開始的 Start 字段指定了。假定定義在 Start 字段中的值大於或者等於 0。如果你傳遞一個正數,將播放 Duration 字段定義的一段直播流。之後,變爲可播放狀態,或者播放 Duration 字段定義的一段錄製流。(如果流在 Duration 字段定義的時間段內結束,那麼流結束時回放結束)。如果你在 Duration 字段中傳遞一個 -1 以外的負數的話,它將把你給的值當做 -1 處理。
Reset布爾一個可選的布爾值或者數字定義了是否對以前的播放列表進行 flush。

Message flow in the play command

        命令執行時的消息流動是爲:
                1. 當客戶端從服務器端接收到 createStream 命令的結果是爲 success 時,發送 play 命令。
                2. 一旦接收到 play 命令,服務器端發送一個協議消息來設置塊大小。
                3. 服務器端發送另一個協議消息 (用戶控制),這個消息中定義了 'StreamIsRecorded' 事件和流 ID。消息在前兩個字節中保存事件類型,在後四個字節中保存流 ID。
                4. 服務器端發送另一個協議消息 (用戶控制),這一消息包含 'StreamBegin' 事件,來指示發送給客戶端的流的起點。
                5. 如果客戶端發送的 play 命令成功,服務器端發送一個 onStatus 命令消息 NetStream.Play.Start & NetStream.Play.Reset。只有當客戶端發送的 play 命令設置了 reset 時服務器端纔會發送 NetStream.Play.Reset。如果要播放的流沒有找到,服務器端發送 onStatus 消息 NetStream.Play.StreamNotFound。
        之後,服務器端發送視頻和音頻數據,客戶端對其進行播放。
        7.2.2.2. play2
        不同於 play 命令的是,play2 可以在不改變播放內容時間軸的情況下切換到不同的比特率。服務器端爲客戶端可以在 play2 中請求所有支持的碼率維護了不同的字段。
        客戶端發送給服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名,設置爲 "play2"。
Transaction ID數字事務 ID 設置爲 0。
Command ObjectNull命令信息不存在,設置爲 null 類型。
Parameters對象一個 AMF 編碼的對象,該對象的屬性是爲公開的 flash.net.NetStreamPlayOptions ActionScript 對象所描述的屬性。


        NetStreamPlayOptions 對象的公開屬性在 ActionScript 3 語言指南中 [AS3] 有所描述。
        命令執行時的消息流動如下圖所示:
Message flow in the play2 command

        7.2.2.3. deleteStream 命令
        當 NetStream 對象消亡時 NetStream 發送 deleteStream 命令
        客戶端發送給服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名,設置爲 "deleteStream"。
Transaction ID數字事務 ID 設置爲 0。
Command ObjectNull命令信息對象不存在,設爲 null 類型。
Stream ID數字服務器端消亡的流 ID。


        服務器端不再發送任何回覆。
        7.2.2.4. receiveAudio 命令
        NetStream 通過發送 receiveAudio 消息來通知服務器端是否發送音頻到客戶端
        客戶端發送給服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名,設置爲 "receiveAudio"。
Transaction ID數字事務 ID 設置爲 0。
Command ObjectNull命令信息對象不存在,設置爲 null 類型。
Bool Flag布爾true 或者 false 以表明是否接受音頻。


        如果發送來的 receiveAudio 命令布爾字段被設爲 false 時服務器端不發送任何回覆。如果這一標識被設爲 true,服務器端以狀態消息 NetStream.Seek.Notify 和 NetStream.Play.Start 進行回覆。
        7.2.2.5. receiveVideo 命令
        NetStream 通過發送 receiveVideo 消息來通知服務器端是否發送視頻到客戶端
        客戶端發送給服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名,設置爲 "receiveVideo"。
Transaction ID 數字事務 ID 設置爲 0。
Command ObjectNull命令信息對象不存在,設置爲 null 類型。
Bool Flag布爾true 或者 false 以表明是否接受視頻。


        如果發送來的 receiveVideo 命令布爾字段被設爲 false 時服務器端不發送任何回覆。如果這一標識被設爲 true,服務器端以狀態消息 NetStream.Seek.Notify 和 NetStream.Play.Start 進行回覆。
        7.2.2.6. publish 命令
        客戶端發送給服務器端這一命令以發佈一個已命名的流。使用這個名字,任意客戶端都可以播放這個流,並接受發佈的音頻、視頻以及數據消息。
        客戶端發送給服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名,設置爲 "publish"。
Transaction ID數字事務 ID 設置爲 0。
Command ObjectNull命令信息對象不存在,設置爲 null 類型。
Publishing Name字符串發佈的流的名字。
Publishing Type字符串發佈類型。可以設置爲 "live"、"record" 或者 "append"。record:流被髮布,數據被錄製到一個新的文件。新文件被存儲在服務器上包含服務應用目錄的子路徑。如果文件已存在,將重寫。append:流被髮布,數據被添加到一個文件。如果該文件沒找着,將新建一個。live:直播數據只被發佈,並不對其進行錄製。


        服務器端回覆 onStatus 命令以標註發佈的起始位置。
        7.2.2.7. seek 命令
        客戶端發送 seek 命令以查找一個多媒體文件或一個播放列表的偏移量 (以毫秒爲單位)
        客戶端發送到服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令的名字,設爲 "seek"。
Transaction ID數字事務 ID 設爲 0。
Command ObjectNull沒有命令信息對象,設置爲 null 類型。
milliSeconds數字播放列表查找的毫秒數。


        seek 命令執行成功時服務器會發送一個狀態消息 NetStream.Seek.Notify。失敗的話,服務器端返回一個 _error 消息。
        7.2.2.8. pause 命令
        客戶端發送 pause 命令以告知服務器端是暫停還是開始播放。
        客戶端發送給服務器端的命令結構如下:
字段名類型描述
Command Name字符串命令名,設爲 "pause"。
Transaction ID數字沒有這一命令的事務 ID,設爲 0。
Command ObjectNull命令信息對象不存在,設爲 null 類型。
Pause/Unpause Flag布爾true 或者 false,來指示暫停或者重新播放。
milliSeconds數字流暫停或者重新開始所在的毫秒數。這個是客戶端暫停的當前流時間。當回放已恢復時,服務器端值發送帶有比這個值大的 timestamp 消息。


        當流暫停時,服務器端發送一個狀態消息 NetStream.Pause.Notify。NetStream.Unpause.Notify 只有針對沒有暫停的流進行發放。失敗的話,服務器端返回一個 _error 消息。

       

7.3. 消息交換例子

        這裏有幾個解釋使用 RTMP 交換消息的例子。

        7.3.1. 發佈錄製視頻

        這個例子說明了一個客戶端是如何能夠發佈一個直播流然後傳遞視頻流到服務器的。然後其他客戶端可以對發佈的流進行訂閱並播放視頻。
Message flow in publishing a video stream

        7.3.2. 廣播一個共享對象消息

        這個例子說明了在一個共享對象的創建和改變期間交換消息的變化。它也說明了共享對象消息廣播的處理過程。
Shared object message broadcast

        7.3.3. 發佈來自錄製流的元數據

        這個例子描述了用於發佈元數據的消息交換。
Publishing metadata

       

8. 參考文獻

        [RFC0791] Postel, J., "Internet Protocol", STD 5, RFC 791, September 1981.
        [RFC0793] Postel, J., "Transmission Control Protocol", STD 7,RFC 793, September 1981.
        [RFC1982] Elz, R. and R. Bush, "Serial Number Arithmetic", RFC 1982, August 1996.
        [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
        [AS3] Adobe Systems, Inc., "ActionScript 3.0 Reference for the Adobe Flash Platform", 2011, <http://www.adobe.com/devnet/actionscript/documentation.html>.
        [AMF0] Adobe Systems, Inc., "Action Message Format -- AMF 0", December 2007, <http://opensource.adobe.com/wiki/download/attachments/1114283/amf0_spec_121207.pdf>.
        [AMF3] Adobe Systems, Inc., "Action Message Format -- AMF 3", May 2008, <http://opensource.adobe.com/wiki/download/attachments/1114283/amf3_spec_05_05_08.pdf>.
        作者地址
        Hardeep Singh Parmar (editor)
        Adobe Systems Incorporated
        345 Park Ave
        San Jose, CA 95110-2704
        US


        Phone: +1 408 536 6000
        Email: [email protected]
        URI: http://www.adobe.com/


        Michael C. Thornburgh (editor)
        Adobe Systems Incorporated
        345 Park Ave
        San Jose, CA 95110-2704
        US


        Phone: +1 408 536 6000
        Email: [email protected]
        URI: http://www.adobe.com/
原文鏈接:http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章