2016年6月6日 星期一

RTMP Spec 心得整理

本篇是讀完RTMP Specification 1.0 後的心得整理。會以 spec為主並搭配 wireshark  rtmp_sample 封包為例,介紹一個基本的 RTMP 播放流程。

RTMP(Real Time Messaging Protocol)即時消息傳送協定是Adobe Systems公司
為Flash播放程式和伺服器之間音訊、視頻和資料傳輸 開發的開放協議,共有五種變體:
  1. RTMP:此標準協定會運行在 TCP 協定上,並且使用埠1935;
  2. RTMPS:RTMP 運行在 TLS/SSL 上。
  3. RTMPE:RTMP 運行在 Adobe 自行定義的加密協定上。
  4. RTMPT:使用 HTTP 將封包進行封裝以穿越防火牆;此時可以使用 RTMP, RTMPS 或 RTMPE。
  5. RTMFP:RTMP 運行在 UDP 上。
RTMP會將串流切割成一個一個的小片段(fragments),每個片段的大小可以動態決定。但有時候會不切割串流資料。預設的 Audio Fragments = 64 bytes, Video Fragments = 128 bytes。

RTMP資料結構由一個可變長度的 header 與 body 組成,其 Header 結構如下
RTMP header (chunk header) = Basic Header | Message Header | Extended Timestamp,Basic Header中的最前面兩個 bits (FMT) 會決定後面的 Message Header type,根據不同的 Type,整個 Header 長度是變動的,可能會是 12, 8, 4, 1 bytes。後面會詳加介紹。


流程概要:
一個 RTMP連線,會先透過 handshake 確認雙方無誤,接著透過 Protocol Message 設定傳輸時的參數(如:chunk size, bandwidth),接著使用 RTMP Message進行傳送。

一、RTMP handshake
RTMP傳送資料之前會先作個非常簡單的 hanshake,此流程由三個固定大小的訊息所構成,如下圖:
此圖片取自 wiki
Client 會送出 C0, C1, C2
Server 會送出 S0, S1, S2 
C0是 RTMP 協定版本號碼,固定為 0x03,大小為 1 octet。 
C1是 client timestamp + 4 zeros + 1536bytes random number。當 Server 收到 C1 之後,應該將 4 zeros 改成 server timestamp 欄位,然後回覆另一個封包 S2。此封包大小固定為 1544 octet。 
C2是當 Client 端收到 server 的 S1時,所回覆的封包,此時內容是 server timestamp + client timestamp + 1536bytes random number,此封包大小固定為 1544 octet。
參考下例,此處我已將C1與S2的差異處用紅色矩形標出,此時 S2 修改了 C1 的 server timestamp (0000000037) 並且回傳。
C0+C1
S0+S1+S2
注意:此處 timestamp 採用 milliseconds,因此 49 天之後數字會 roll over. 此時可參考 [RFC1982] 進行處理。原文如下:
Because timestamps are 32 bits long, they roll over every 49 days, 17hours, 2 minutes and 47.296 seconds.

實測發現許多廠商的 RTMP handshake並未依照上述作法,以 ffmpeg rtmpproto.c為例,說明其作法
首先Client 會先送出 C0, C1
C0是 RTMP 協定版本號碼,固定為 0x03,大小為 1 octet。  
C1是 4 zeros + 4 bytes fixed value(09 00 7c 02)+ 1536bytes random data
** 根據 C1 前四個 bytes 是否為零,就可得知是否是 ffmpeg 送出的資料。
** 注意:ffmpeg 的亂數並不亂,跟某伺服器測試過程,第一次總是產生同樣的 random data。 
當 Server 收到 C1 之後,會回 S0, S1, S2, 其中
S0: 是 Server 端的RTMP 協定版本號碼 
S1: ffmpeg 會取出C1前四個Bytes,當成 hs_my_epoch,組合成下列格式後回傳
hs_my_epoch + 4 zeros + 1536bytes random data。 
S2: S2 會與 C1 完全相同,原始碼如下:
rtmp_send_hs_packet(rt, hs_epoch, 0, hs_c1, RTMP_HANDSHAKE_PACKET_SIZE); 
當 Client 端收到 server 的 S0, S1, S2 時,過程如下
ffmpeg稱 S1 為 serverdata,稱 S2 為 clientdata, 
首先需要檢查 serverdata 的第 5個 byte,若此值大於3,則表示 serverdat 內含 digest,則要取後半部的 random data 驗證 digest 是否正確,否則不做任何驗證,直接將 serverdata 當成 C2 回傳。 
若需要驗證,則 serverdata 與 clientdata 內都會各帶 32bytes的 digest。ffmpeg 會分別使用預先定義的 rtmp_server_key 與 rtmp_player_key 進行驗算。
當 Server 收到 C2 之後,
檢查C2前四個Bytes(hs_my_epoch)是否與S1前四個Bytes相同,並比較後面的1536bytes 是否相同

二、Chunking
Chunking 指的是將大訊息切割成許多小訊息。在Hanshake 流程成功之後,便可開始切割資料,並開始傳送。此處因為 Basic header 的設計限制,因此最多可以同時支援 65597 條 stream (3~65599)。每個Chunk 都會封裝成下列形式。
  下圖中 fmt | stream id 屬於 Basic Header,其他部分屬於 Message Header。
首先來看 Chunk Message Header 如下圖:
Chunk Message Header

Basic Header最前面兩個 bits 表示 FMT(Chunk Type) ,此值會影響 message header的大小。
FMT = 0,message header = timestamp(3) |mesage_length(3) |mesage_type_id(1) | msg_stream_id(4) 
假設要啟動一條 video stream, 首先要先送出便是一個這樣完整的 message header(11 bytes),  
FMT = 1,message header = timestamp_delta(3) |mesage_length(3) |mesage_type_id(1)
接著因為後續的封包屬於同一條 stream, 可以省略 stream_id(4),只送出 7 bytes的 message header。 
FMT = 2,message header = timestamp_delta(3)
如果是固定長度訊息(constant-sized messages),例如:audio stream,那麼可以再省略 mesage_length(3) |mesage_type_id(1),只送出 3 bytes 的 message header。 
FMT = 3,no message header
若單一訊息被拆成多個 chunks, 後續 chunks 可沿用第一個 chunk的所有資訊。此時應該使用此格式。
When a single message is split into chunks, all chunks of a message except the first one SHOULD use this type.
針對 FMT=1 或 2 的格式,若兩個 chunk 的 timestamp 差異大於 16777215 (hexadecimal 0xFFFFFF),表示此時封包會存在 Extended Timestamp field
For a type-1 or type-2 chunk, the difference between the previous chunk’s timestamp and the current chunk’s timestamp is sent here. If the delta is greater than or equal to 16777215 (hexadecimal 0xFFFFFF), this field MUST be 16777215, indicating the presence of the Extended Timestamp field to encode the full 32 bit delta. Otherwise, this field SHOULD be the actual delta.
摘錄 rtmp_specification_1.0 example1 (audio stream),此串流初始化時,前兩個封包多了 12+4=16 bytes,後續每個封包都只需要多加 1bytes 即可。這個做法可以很節省頻寬。
注意: 當 Stream ID = 2 時有特殊用途。
Chunk Stream ID with value 2 is reserved for low-level protocol control messages and commands.

三、RTMP protocol control messages (FMT必定為0)
當 Message Header 為以下組合時,表示此封包是 protocol control messages,此時 timestamp可以省略
message stream ID: 0
message type IDs: 1~6
可根據 message type ID,將protocol control messages 分為五類功能 
  • Set Chunk Size(1)
    • defaults to 128 bytes,Valid sizes are 1 to 16777215(0xFFFFFF),此限制是因為 message length只有 3 bytes.
  • Abort Message(2)
    • used to notify the peer if it is waiting for chunks to complete a message, then to discard the partially received message over a chunk stream.
  • Acknowledgement (3)
    • an acknowledgment to the peer after receiving bytes equal to the window size.The window size is the maximum number of bytes that the sender sends without receiving acknowledgment from the receiver.
  • Window Acknowledgement Size (5)
    • to inform the peer of the window size to use between sending acknowledgments.
  • Set Peer Bandwidth (6)
    • The client or the server sends this message to limit the output bandwidth of its peer.
上述五個功能屬於 RTMP Chunk Stream protocol,而 Type(4) 的用途則是在 RTMP streaming layer 傳送 User control message,雖然都是 control message,但使用時機不同,所以另外介紹。 

四、RTMP messages format
RTMP messages 可用來包裝 audio, video, data 或其他資訊,由 header+payload 組成,其header如下:
注意:此處 Message Type 欄位,已經將 1~6 保留給 protocol control messages

五、RTMP User Control Messages
RTMP User Control messages是 Client 與 Server 彼此通知訊息用的,此時Stream ID = 0, Chunk Stream ID = 2, message type IDs = 4,,此訊息 payload 如下: 
Event Type(16bit) | Event Data
根據不同的 Event Type 會有不同的 Event Data 長度,此處共定義了6種event
  • Stream Begin(=0),data欄位為 4-byte stream id
  • Stream EOF(=1),data欄位為 4-byte stream id 
  • StreamDry(=2) ,data欄位為 4-byte stream id
  • Stream BeginSetBufferLength(=3),data欄位為 4-byte stream id + 4-byte buffer length
  • StreamIsRecorded(=4),data欄位為 4-byte stream id
  • PingRequest(=6),data欄位為 4-byte timestamp
  • PingResponse(=7),data欄位為 4-byte timestamp


六、RTMP Command Messages
RTMP  message format 除了包裝上述的 User control message 之後,還可以包裝 command messages. 其定義如下:  
Command messages carry the AMF encoded commands between the client and the server. 
此處AMF 指的是 Action Message Format 
Command Messages 可分為以下六類
  • Audio Message (8)
  • Video Message (9)
  • Data Message (18, 15)
    • Includes details about the data(audio, video etc.) like creation time, duration, theme and so on. 
    • 18 for AMF0 and message type value of 15 for AMF3. 
  • Command Message (20, 17)
    • Operations like connect, createStream, publish, play, pause, onstatus, result.
    • 20 for AMF0 encoding and 17 for AMF3 encoding. 
  • Shared Object Message (19, 16)
    • A shared object is a Flash object (a collection of name value pairs) that are in synchronization across multiple clients, instances, and so on. The message types 19 for AMF0 and 16 for AMF3 are reserved 
  • Aggregate Message (22)
    • a single message that contains a series of RTMP sub-messages.

七、Types of Commands
Command Message 由以下三者所構成
  • command name, 
  • transaction ID,
  • command object that contains related parameters
根據 command 的用途,又可以將其分為 NetConncetion 與 NetStream 兩大類,整理如下:
NetConnection Commands
  • connect 
    • to request connection to a server application instance.
  • call
    • runs remote procedure calls (RPC) at the receiving end.
  • close
  • createStream
    • The client sends this command to the server to create a logical channel for message communication The publishing of audio, video, and metadata is carried out over stream channel created using the createStream command.
 
NetStream Commands
  • play
    • The client sends this command to the server to play a stream
  • play2
    • Unlike the play command, play2 can switch to a different bit rate stream without changing the timeline of the content played.
  • deleteStream
    • NetStream sends the deleteStream command when the NetStream object is getting destroyed.
  • closeStream
  • receiveAudio
    • NetStream sends the receiveAudio message to inform the server whether to send or not to send the audio to the client.
  • receiveVideo
    • NetStream sends the receiveVideo message to inform the server whether to send the video to the client or not.
  • publish
    • The client sends the publish command to publish a named stream to the server. 
  • seek
    • The client sends the seek command to seek the offset (in milliseconds) within a media file or playlist.
  • pause
    • The client sends the pause command to tell the server to pause or start playing.
Note:The server sends NetStream status updates to the client using the "onStatus" command:
一個成功建立連線的例子如下,若要更多例子可參考 rtmp_specification_1.0.pdf 


八、補充 AMF encoded message
Action Message Format (AMF)
A compact binary format that is used to serialize ActionScript object graphs. AMF has two versions: AMF 0 [AMF0] and AMF 3 [AMF3].
AMF message type 範例
0x01 = Set Packet Size Message.
0x04 = Ping Message.
0x05 = Server Bandwidth
0x06 = Client Bandwidth.
0x08 = Audio Packet.
0x09 = Video Packet.
0x11 = An AMF3 type command.
0x12 = Invoke (onMetaData info is sent as such).
0x14 = An AMF0 type command.

註1: RTMP 中傳送 H.264 使用的 video 封裝格式就是 FLV(Flash Video)規範中VideoTag
註2: 若要透過 FFMpeg 播放 rtmp,範例如下:
ffplay  rtmp://[username:password@]server[:port][/app][/instance][/playpath]

參考資料:
  1. RTMP WIKI 
  2. Wireshark 所提供的RTMP範例
  3. Adobe 所定義的 RTMP規範
  4. librtmp source code
  5. Flash Media Live Encoder 3.2