国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

RTMP H5 直播流技術解析

李世贊 / 2019人閱讀

摘要:上一篇文章簡單闡述了,在中,做直播需要哪些技術知識點,有哪些直播流協議和技術。搞了一個比較繞的理論,即,通過如下格式中的來確定即,通過位來確定整個的長度。例如最后強調一下,因為規定,的為保留字,所以,不作為。全稱為協議控制消息。

上一篇文章簡單闡述了,在 H5 中,做直播需要哪些技術知識點,有哪些直播流協議和技術。通過對比,本篇主要聚焦于 RTMP 直播協議的相關內容,也就是說,本篇將會直接進行實際操作 Buffer 的練習和相關的學習。

RTMP 是什么

RTMP 全稱即是 Real-Time Messaging Protocol。顧名思義就是用來作為實時通信的一種協議。該協議是 Adobe 搞出來的。主要是用來傳遞音視頻流的。它通過一種自定義的協議,來完成對指定直播流的播放和相關的操作。和現行的直播流相比,RTMP 主要的特點就是高效,這里,我就不多費口舌了。我們先來了解一下 RTMP 是如何進行握手的。

RTMP 握手

RTMP 是基于 TCP 三次握手之后的,所以,RTMP 不是和 TCP 一個 level 的。它本身是基于 TCP 的可靠性連接。RTMP 握手的方式如圖:

(C 代表 Client,S 代表 Server)

它主要是通過兩端的字段內容協商,來完成可信度認證的?;具^程如下:

client: 客戶端需要發 3 個包。C0,C1,C2

server: 服務端也需要發同樣 3 個包。 S0,S1,S2。

整個過程如上圖所述,但實際上有些細節需要注意。

握手開始:

【1】 客戶端發送 C0,C1 包

此時,客戶端處于等待狀態。客戶端有兩個限制:

客戶端在未接受到 S1 之前不能發送 C2 包

客戶端在未接收到 S2 之前不能發送任何實際數據包

【2】 服務端在接受到 C0,發送 S0,S1 包。也可以等到接受到 C1 之后再一起發送,C1 包的等待不是必須的。

此時,服務端處于等待狀態。服務端有兩個限制:

服務端在未接受到 C1 之前不能發送 S2.

服務端在未接收到 C2 之前不能發送任何實際數據包

【3】客戶端接受到 S1/S0 包后,發送 C2 包。

【4】服務端接受到 C2 包后,返回 S2 包,并且此時握手已經完成。

不過,在實際應用中,并不是嚴格按照上面的來。因為 RTMP 并不是強安全性的協議,所以,S2/C2 包只需要 C1/S1 中的內容,就可以完成內容的拼接。

這么多限制,說白了,其實就是一種通用模式:

C0+C1

S0+S1+S2

C2

接下來,我們來具體看看 C/S 012 包分別代表什么。

C0 && S0

C0 和 S0 其實區別不大,我這里主要講解一下 C0,就差不多了。首先,C0 的長度為 1B。它的主要工作是確定 RTMP 的版本號。

C0:客戶端發送其所支持的 RTMP 版本號:3~31。一般都是寫 3。

S1:服務端返回其所支持的版本號。如果沒有客戶端的版本號,默認返回 3。

C1 && S1

C1/S1 長度為 1536B。主要目的是確保握手的唯一性。格式為:

time: 發送時間戳,這個其實不是很重要,不過需要記住,不要超出 4B 的范圍即可。

zero: 保留值 0.

random: 該字段長尾 1528B。主要內容就是隨機值,不管你用什么產生都可以。它主要是為了保證此次握手的唯一性,和確定握手的對象。

C2 && S2

C2/S2 的長度也是 1536B。相當于就是 S1/C1 的響應值。上圖也簡單說明了就是,對應 C1/S1 的 Copy 值,不過第二個字段有區別?;靖袷綖椋?/p>

time: 時間戳,同上,也不是很重要

time2: C1/S1 發送的時間戳。

random: S1/C1 發送的隨機數。長度為 1528B。

這里需要提及的是,RTMP 默認都是使用 Big-Endian 進行寫入和讀取,除非強調對某個字段使用 Little-Endian 字節序。

上面握手協議的順序也是根據其中相關的字段來進行制定的。這樣,看起來很容易啊哈,但是,我們并不僅僅停留在了解,而是要真正的了解,接下來,我們來實現一下,如果通過 Buffer 來進行 3 次握手。這里,我們作為 Client 端來進行請求的發起,假設 Server 端是按照標準進行發送即可。

Buffer 實操握手

我們使用 Buffer 實操主要涉及兩塊,一個塊是 request server 的搭建,還有一塊是 Buffer 的拼接。

Request Server 搭建

這里的 Server 是直接使用底層的 TCP 連接。

如下,一個簡易的模板:

const client = new net.Socket();

client.connect({
    port: 1935,
    host: "6721.myqcloud.com"},
    ()=>{
        console.log("connected");
    });
    
client.on("data",(data)=>{
    client.write("hello");
});

不過,為了更好的進行實際演練,我們通過 EventEmitter 的方式,來做一個篩選器。這里,我們使用 mitt 模塊來做代理。

const Emitter = require("mitt")();

然后,我們只要分析的就是將要接受到的 S0/1/2 包。根據上面的字節包圖,可以清楚的知道包里面的詳細內容。這里,為了簡單起見,我們排除其他協議的包頭,只是針對 RTMP 里面的包。而且,我們針對的只有 3 種包,S0/1/2。為了達到這種目的,我們需要在 data 時間中,加上相應的鉤子才行。

這里,我們借用 Now 直播的 RTMP 流來進行相關的 RTMP 直播講解。

Buffer 操作

Server 的搭建其實上網搜一搜,應該都可以搜索出來。關鍵點在于,如何針對 RTMP 的實操握手進行 encode/decode。所以,這里,我們針對上述操作,來主要講解一下。

我們主要的工作量在于如何構造出 C0/1/2。根據上面格式的描述,大家應該可以清楚的知道 C0/1/2 里面的格式分別有啥。

比如,C1 中的 time 和 random,其實并不是必須字段,所以,為了簡單起見,我們可以默認設為 0。具體代碼如下:

class C {
    constructor() {
        this.time;
        this.random;
    }
    C0() {
        let buf = Buffer.alloc(1);
        buf[0] = 3;
        return buf;
    }
    C1() {
        let buf = Buffer.alloc(1536);
        return buf;
    }
    /**
     * write C2 package
     * @param {Number} time the 4B Number of time
     * @param {Buffer} random 1528 byte
     */
    produceC2(){
        let buf = Buffer.alloc(1536);
        // leave empty value as origin time
        buf.writeUInt32BE(this.time, 4);
        this.random.copy(buf,8,0,1528);

        return buf;
    }
    get getC01(){
        return Buffer.concat([this.C0(),this.C1()]);
    }
    get C2(){
        return this.produceC2();
    }
}

接下來,我們來看一下,結合 server 完成的 RTMP 客戶端服務。

const Client = new net.Socket();
const RTMP_C = new C();


Client.connect({
    port: 1935,
    host: "6721.liveplay.myqcloud.com"
}, () => {
    console.log("connected")
    Client.write(RTMP_C.getC01);

});

Client.on("data",res=>{
    if(!res){
        console.warn("received empty Buffer " + res);
        return;
    }
    // start to decode res package
    if(!RTMP_C.S0 && res.length>0){
        RTMP_C.S0 = res.readUInt8(0);
        res = res.slice(1);
    }

    if(!RTMP_C.S1 && res.length>=1536){
        RTMP_C.time = res.readUInt32BE(0);
        RTMP_C.random = res.slice(8,1536);
        RTMP_C.S1 = true;
        res = res.slice(1536);
        console.log("send C2");
        Client.write(RTMP_C.C2);
    }

    if(!RTMP_C.S2 && res.length >= 1536){
        RTMP_C.S2 = true;
        res = res.slice(1536);
    }
})

詳細代碼可以參考 gist。

RTMP 基本架構

RTMP 整個內容,除了握手,其實剩下的就是一些列圍繞 type id 的 message。為了讓大家更清楚的看到整個架構,這里簡單陳列了一份框架:

在 Message 下的 3 個一級子 Item 就是我們現在將要大致講解的內容。

可以看到上面所有的 item 都有一個共同的父 Item--Message。它的基本結構為:

Header: header 部分用來標識不同的 typeID,告訴客戶端相應的 Message 類型。另外,還有個功效就是多路分發。

Body: Body 內容就是相應發送的數據。這個根據不同的 typeID 來說,格式就完全不一樣了。

下面,我們先了解一下 Header 和不同 typeID 的內容:

Header

RTMP 中的 Header 分為 Basic Header 和 Message Header。需要注意,他們兩者并不是獨立的,而是相互聯系。Message Header 的結構由 Basic Header 的內容來決定。

接下來,先分開來講解:

Basic Header

BH(基礎頭部)主要是定義了該 chunk stream ID 和 chunk type。需要注意的是,BH 是變長度的,即,它的長度范圍是 1-3B。怎么講呢?就是根據不同的 chunk stream ID 來決定具體的長度。CS ID(Chunk Stream ID)本身的支持的范圍為 <= 65597 ,差不多為 22bit。當然,為了節省這 3B 的內容。 Adobe 搞了一個比較繞的理論,即,通過如下格式中的 CS ID 來確定:

  0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+
 |fmt|   cs id   |
 +-+-+-+-+-+-+-+-+

即,通過 2-7 bit 位來確定整個 BH 的長度。怎么確定呢?

RTMP 規定,CS ID 的 0,1,2 為保留字,你在設置 CS ID 的時候只能從 3 開始。

CS ID: 0 ==> 整個 BH 長為 2B,其中可以表示的 Stream ID 數量為 64-319。例如:

  0 1
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |fmt|    0    |    cs id - 64   |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

注意上面的 cs id - 64。這個代表的就是,你通過切割第二個 byte 時,是將得到的值加上 64。即:2th byte + 64 = CS ID

CS ID: 1 ==> 整個 BH 長為 3B??梢源鎯Φ?Stream ID 數量為 64-65599。例如:

  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          |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

當然,后面 CS ID 的計算方法也是最后的結果加上 64。

CS ID >2 ==> 整個 BH 長為 1B??梢源鎯Φ?Stream ID 數量為 3-63。例如:

  0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+
 |fmt|  cs id    |
 +-+-+-+-+-+-+-+-+

最后強調一下,因為 RTMP 規定,CS ID 的 0,1,2 為保留字,所以,0,1,2 不作為 CS ID。綜上所述,CS ID 的起始位為 3(并不代表它是 3 個 Stream)。

上面我并沒有提到 fmt 字段,這其實是用來定義 Message Header 的。

Message Header

根據前面 BH 中 fmt 字段的定義,可以分為 4 種 MH(Message Header)?;蛘哒f,就是一種 MH 格式會存在從繁到簡 4 種:

fmt: 0

當 fmt 為 0 時,MH 的長度為 11B。該類型的 MH 必須要流的開頭部分,這包括當進行快退或者點播時重新獲取的流。該結構的整體格式如下:

也就是說,當 fmt 為 0 時,其格式是一個完整的 MH。

timestamp 是絕對時間戳。用來代表當前流編碼。

message length: 3B, 發送 message 的長度。

type id: 1B

stream id: 4B, 發送 message stream id 的值。是 little-endian 寫入格式!

fmt: 1

當 fmt 為 1 時,MH 的長度為 7B。該類型的 MH 不帶 msg stream id。msg stream id 由前面一個 package 決定。該數值主要由前一個 fmt 為 0 的 MH 決定。該類型的 MH 通常放在 fmt 為 0 之后。

fmt: 2

當 fmt 為 2 時,MH 的長度為 3B。該類型的 MH 只包括一個 timestamp delta 字段。其它的信息都是依照前面一個其他類型 MH 決定的。

fmt: 3

當 fmt 為 3時,這其實 RTMP 里面就沒有了 MH。官方定義,該類型主要全部都是 payload 的 chunk,其 Header 信息和第一個非 type:3 的頭一致。因為這主要用于 chunk 中,這些 chunk 都是從一個包里面切割出來的,所以除了第一個 chunk 外,其它的 chunk 都可以采用這種格式。當 fmt 為 3時,計算它的 timestamp 需要注意幾點,如果前面一個 chunk 里面存在 timestrameDelta,那么計算 fmt 為 3 的 chunk 時,就直接相加,如果沒有,則是使用前一個 chunk 的 timestamp 來進行相加,用代碼表示為:

prevChunk.timeStamp += prevChunk.timeStampDelta || prevChunk.timeStamp;

不過,當 fmt: 3 的情況一般很難遇到。因為,他要求前面幾個包必須存在 fmt 為 0/1/2 的情況。

接下來的就是 Message Body 部分。

Message Body

上面說的主要是 Message Header 的公用部分,但是,對于具體的 RTMP Message 來說,里面的 type 會針對不同的業務場景有不同的格式。Message 全部內容如上圖所示:

這里,我們根據流程圖的一級子 item 來展開講解。

PCM

PCM 全稱為:Protocol Control Messages(協議控制消息)。主要使用來溝通 RTMP 初始狀態的相關連接信息,比如,windows size,chunk size 等。

PCM 中一共有 5 種不同的 Message 類型,是根據 Header 中的 type ID 決定的,范圍是 1~6 (不包括 4)。另外,PCM 在構造的時候需要注意,它 Heaer 中的 message stream id 和 chunk stream id 需要設置為固定值:

message stream ID 為 0

chunk stream ID 為 2

如圖所示:

OK,我們接下來一個一個來介紹一下:

Set Chunk Size(1)

看名字大家應該都能猜到這類信息是用來干啥的。該類型的 PCM 就是用來設置 server 和 client 之間正式傳輸信息的 chunk 的大小,type ID 為 1。那這有啥用呢?

SCS(Set Chunk Size) 是針對正式發送數據而進行數據大小的發送限制。一般默認為 128B。不過,如果 server 覺得太小了,想發送更大的包給你,比如 132B,那么 server 就需要給你發送一個 SCS,告知你,接下來“我發送給你的數據大小是 132B”。

0: 只能設為 0 ,用來表示當前的 PCM 的類型。

chunk size: 用來表示后面發送正式數據時的大小。范圍為 1-16777215。

如下,提供過 wireshark 抓包的結果:

Abort Message(2)

該類 PCM 是用來告訴 client,丟棄指定的 stream 中,已經加載到一半或者還未加載完成的 Chunk Message。它需要指定一個 chunk stream ID。

基本格式為:

chunk stream id: 指定丟棄 chunk message 的 stream

Acknowledgement(3)

該協議信息其實就是一個 ACK 包,在實際使用是并沒有用到,它主要是用來作為一個 ACK 包,來表示兩次 ACK 間,接收端所能接收的最大字節數。

它基本格式為:

sequence number[4B]: 大小為 4B

不過,該包在實際應用中,沒有多高的出現頻率。

Window Acknowledgement Size(5)

這是用來協商發送包的大小的。這個和上面的 chunk size 不同,這里主要針對的是客戶端可接受的最大數據包的值,而 chunk size 是指每次發送的包的大小。也可以叫做 window size。一般電腦設置的大小都是 500000B。

詳細格式為:

通過,wireshark 抓包的結果為:

Set Peer Bandwidth(6)

這是 PCM 中,最后一個包。他做的工作主要是根據網速來改變發送包的大小。它的格式和 WAS 類似,不過后面帶上了一個 Type 用來標明當前帶寬限制算法。當一方接收到該信息后,如果設置的 window size 和前面的 WAS 不一致,需要返回一個 WAS 來進行顯示改變。

基本格式為:

其中 Limit Type 有 3 個取值:

0: Hard,表示當前帶寬需要和當前設置的 window size 匹配

1: Soft,將當前寬帶設置為該信息定義的 window size,或者已經生效的 window size。主要取決于誰的 window size 更小

2: Dynamic,如果前一個 Limit Type 為 Hard 那么,繼續使用 Hard 為基準,否則忽略該次協議信息。

實際抓包情況可以參考:

UCM

全稱為:User Control Message(用戶控制信息)。它的 Type ID 只能為 4。它主要是發送一些對視頻的控制信息。其發送的條件也有一定的限制:

msg stream ID 為 0

chunk stream ID 為 2

它的 Body 部分的基本格式為:

UCM 根據 Event Type 的不同,對流進行不同的設置。它的 Event Type 一共有 6 種格式 Stream Begin(0),Stream EOF(1),StreamDry(2),SetBuffer Length(3),StreamIs Recorded(4)PingRequest(6),PingResponse(7)

這里,根據重要性劃分,只介紹 Begin,EOF,SetBuffer Length 這 3 種。

Stream Begin: Event Type 為 0。它常常出現在,當客戶端和服務端成功 connect 后發送。Event Data 為 4B,內容是已經可以正式用來傳輸數據的 Stream ID(實際沒啥用)。

Stream EOF: Event Type 為 1。它常常出現在,當音視頻流已經全部傳輸完時。 Event Data 為 4B,用來表示已經發送完音視頻流的 Stream ID(實際沒啥用)。

Set Buffer Length: Event Type 為 3。它主要是為了通知服務端,每毫秒用來接收流中 Buffer 的大小。Event Data 的前 4B 表示 stream ID,后面 4B 表示每毫秒 Buffer 的大小。通常為 3000ms

OK 剩下就是 Command Msg 里面的內容了。

Command Msg

Command Msg 里面的內容,其 type id 涵蓋了 8~22 之間的值。具體內容,可以參考下表:

需要注意,為什么有些選項里面有兩個 id,這主要和 AMF 版本選擇有關。第一個 ID 表示 AMF0 的編解碼方式,第二個 ID 表示 AMF3 的編解碼方式。
其中比較重要的是 command Msg,video,audio 這 3 個 Msg。為了讓大家更好的理解 RTMP 流的解析,這里,先講解一下 video 和 audio 兩個 Msg。

Video Msg

因為 RTMP 是 Adobe 開發的。理所當然,內部的使用格式肯定是 FLV 格式。不過,這和沒說一樣。因為,FLV 格式內部有很多的 tag 和相關的描述信息。那么,RTMP 是怎么解決的呢?是直接傳一整個 FLV 文件,還自定義協議來分段傳輸 FLV Tag 呢?

這個其實很好回答,因為 RTMP 協議是一個長連接,如果是傳整個 FLV 文件,根本沒必要用到這個,而且,RTMP 最常用在直播當中。直播中的視頻都是分段播放的。綜上所述,RTMP 是根據自己的自定義協議來分段傳輸 FLV Tag 的。那具體的協議是啥呢?

這個在 RTMP 官方文檔中其實也沒有給出。它只是告訴我們 Video Msg 的 type ID 是 9 而已。

因為,RTMP 只是一個傳輸工具,里面傳什么還是由具體的流生成框架來決定的。所以,這里,我選擇了一個非常具有代表性的 RTMP 直播流來進行講解。

通過 wireshark 抓包,可以捕獲到以下的 RTMP 包數據:

這里需要提及一點,因為 RTMP 是主動將 Video 和 Audio 分開傳輸,所以,它需要交叉發布 Video 和 Audio,以保證音視頻的同步。那么具體每個 Video Data 里面的數據都是一樣的嗎?

如果看 Tag 的話,他們傳輸的都是 VideoData Tag。先看一下 FLV VideoData Tag 的內容:

這是 FLV Video 的協議格式。但,遇到第一個字段 FrameType 的時候,我們就可能懵逼了,這 TM 有 5 種情況,難道 RTMP 會給你 5 種不同的包嗎?

答案是,有可能,但是,很大情況下,我們只需要支持 1/2 即可。因為,視頻中最重要的是 I 幀,它對應的 FrameType 就是 1。而 B/P 則是剩下的 2。我們只要針對 1/2 進行軟解,即可實現視頻所有信息的獲取。

所以,在 RTMP 中,也主要(或者大部分)都是傳輸上面兩種 FrameType。我們通過實際抓包來講解一下。

這是 KeyFrame 的包,注意 Buffer 開頭的 17 數字。大家可以找到上面的 FrameType 對應找一找,看結果是不是一致的:

這是 Inter-frame 的包。同上,大家也可以對比一下:

Audio Tag

Aduio Tag 也是和 Video Tag 一樣的蜜汁數據。通過觀察 FLV Audio Tag 的內容:

上面這些字段全是相關的配置值,換句話說,你必須實現知道這些值才行。這里,RTMP 發送 Audio Tag 和 Video Tag 有點不同。因為 Audio Tag 已經不可能再細分為 Config Tag,所以,RTMP 會直接傳遞 上面的 audio Tag 內容。詳細可以參考抓包內容:

這也是所有的 Audio Msg 的內容。

因為 Audio 和 Video 是分開發送的。所以,在后期進行拼接的時候,需要注意兩者的同步。說道這里,順便補充一下,音視頻同步的相關知識點。

音視頻同步

音視頻同步簡單來說有三種:

以 Audio 為準,Video 同步 Audio

以 Video 為準,Audio 同步 Video

以外部時間戳為準,AV 同時同步

主要過程變量參考就是 timeStampduration。因為,這里主要是做直播的,推薦大家采用第二種方法,以 Video 為準。因為,在實際開發中,會遇到 MP4 文件生成時,必須要求第一幀為 keyframe,這就造成了,以 Audio 為參考的,會遇到兩個變量的問題。一個是 timeStamp 一個是 keyframe。當然,解決辦法也是有的,就是檢查最后一個拼接的 Buffer 是不是 Keyframe,然后判斷是否移到下一次同步處理。

這里,我簡單的說一下,以 Video 為準的同步方法。以 Video 同步,不需要管第一幀是不是 keyframe,也不需要關心 Audio 里面的數據,因為,Audio 數據是非常簡單的 AAC 數據。下面我們通過偽代碼來說明一下:

// known condition
video.timeStamp && video.perDuration && video.wholeDuration
audio.timeStamp && audio.perDuration

// start
refDuration = video.timeStamp + video.wholeDuration
delta = refDuration - audio.timeStamp
audioCount = Math.round(delta/audio.perDuration);
audDemuxArr = this._tmpArr.splice(0,audioCount);

// begin to demux
this._remuxVideo(vidDemuxArr);
this._remuxAudio(audDemuxArr);

上面算法可以避免判斷 Aduio 和 Video timeStamp 的比較,保證 Video 一直在 Audio 前面并相差不遠。下面,我們回到 RTMP 內容。來看看 Command Msg 里面的內容。

Command Msg

Command Msg 是 RTMP 里面的一個主要信息傳遞工具。常常用在 RTMP 前期和后期處理。Command Msg 是通過 AMF 的格式進行傳輸的(其實就是類似 JSON 的二進制編碼規則)。Command Msg 主要分為 net connectnet stream 兩大塊。它的交流方式是雙向的,即,你發送一次 net connect 或者 stream 之后,另外一端都必須返回一個 _result 或者 _error 以表示收到信息。詳細結構可以參考下圖:

后續,我們分為兩塊進行講解:

netConnection

netStream

里面的 _result 和 _error 會穿插在每個包中進行講解。

NetConnection

netConnection 可以分為 4 種 Msg,connectcall,createStream,close。

connect

connect 是客戶端向 Server 端發送播放請求的。里面的字段內容有:

Command Name[String]: 默認為 connect。表示信息名稱

Transaction ID[Number]: 默認為 1。

Command Object: 鍵值對的形式存放相關信息。

Optional: 可選值。一般沒有

那,Command Object 里面又可以存放些什么內容呢?

app[String]: 服務端連接應用的名字。這個主要根據你的 RTMP 服務器設定來設置。比如:live

flashver[String]: Flash Player 的版本號。一般根據自己設備上的型號來確定即可。也可以設置為默認值:LNX 9,0,124,2

tcUrl[String]: 服務端的 URL 地址。簡單來說,就是 protocol://host/path。比如:rtmp://6521.liveplay.myqcloud.com/live。

fpad[Boolean]: 表示是否使用代理。一般為 false。

audioCodecs[Number]: 客戶端支持的音頻解碼。后續會介紹。默認可以設置為 4071

videoCodecs[Number]: 客戶端支持的視頻解碼。有自定義的標準。默認可以設置為 252

videoFunction[Number]: 表明在服務端上調用那種特別的視頻函數。默認可以設置為 1

簡單來說,Command Object 就是起到 RTMP Route 的作用。用來請求特定的資源路徑。實際數據,可以參考抓包結果:

上面具體的取值主要是根據 rtmp 官方文檔來決定。如果懶得查,可以直接使用上面的取值。上面的內容是兼容性比較高的值。當該包成功發送時,另外一端需要得到一個返回包來響應,具體格式為:

Command Name[String]: 為 _result 或者 _error。

Transaction ID[Number]: 默認為 1。

Command Object: 鍵值對的形式存放相關信息。

Information[Object]: 鍵值對的形式,來描述相關的 response 信息。里面存在的字段有:level,code,description

可以參考:

connect 包發送的位置,主要是在 RTMP 握手結束之后。如下:

call

call 包主要作用是用來遠程執行接收端的程序(RPC, remote procedure calls)。不過,在我解 RTMP 的過程中,并沒有實際用到過。這里簡單介紹一下格式。它的內容和 connect 類似:

Procedure Name[String]: 調用處理程序的名字。

Transaction ID[Number]: 如果想要有返回,則我們需要制定一個 id。否則為 0。

Command Object: 鍵值對的形式存放相關信息。AMF0/3

Optional: 可選值。一般沒有

Command Object 里面的內容主要是針對程序,設置相關的調用參數。因為內容不固定,這里就不介紹了。

call 一般是需要有 response 來表明,遠端程序是否執行,以及是否執行成功等。返回的格式為:

Command Name[String]: 根據 call 中 Command Object 參數來決定的。

Transaction ID[Number]: 如果想要有返回,則我們需要制定一個 id。否則為 0。

Command Object: 鍵值對的形式存放相關信息。AMF0/3

Response[Object]: 響應的結果值

createStream

createStream 包只是用來告訴服務端,我們現在要創建一個 channel 開始進行流的交流了。格式和內容都不復雜:

Procedure Name[String]: 調用處理程序的名字。

Transaction ID[Number]: 自己制定一個。一般可以設為 2

Command Object: 鍵值對的形式存放相關信息。AMF0/3

當成功后,服務端會返回一個 _result 或者 _error 包來說明接收成功,詳細內容為:

Command Name[String]: 根據 call 中 Command Object 參數來決定的。

Transaction ID[Number]: 如果想要有返回,則我們需要制定一個 id。否則為 0。

Command Object: 鍵值對的形式存放相關信息。AMF0/3。一般為 Null

Stream ID: 返回的 stream ID 值。

它的返回值很隨意,參考抓包內容:

下面,我們來看一下 RTMP 中第二個比較重要的 command msg -- netStream msg。

NetStream Msg

NetStream 里面的 Msg 有很多,但在直播流中,比較重要的只有 play 包。所以,這里我們著重介紹一下 play 包。

play

play 包主要是用來告訴 Server 正式播放音視頻流。而且,由于 RTMP 天然是做多流分發的。如果遇到網絡出現相應的波動,客戶端可以根據網絡條件多次調用 play 命令,來切換不同模式的流。

其基本格式為:

Command Name[String]: 根據 call 中 Command Object 參數來決定的。

Transaction ID[Number]: 默認為 0。也可以設置為其他值

Command Object: 不需要該字段,在該命令中,默認設為 Null

Stream Name[String]: 用來指定播放的視頻流文件。因為,RTMP 天生是支持 FLV 的,所以針對 FLV 文件來說,并不需要加額外的標識,只需要寫明文件名即可。比如:

StreamName: "6721_75994f92ce868a0cd3cc84600a97f75c"

不過,如果想要支持其它的文件,那么則需要額外的表示。當然,音頻和視頻需要不同的支持:

如果是播放音頻文件,比如 mp3,那么則需要額外的前綴標識符-mp3。例如:mp3:6721_75994f9

如果涉及到視頻文件的話,不僅需要前綴,還需要后綴。比如播放的是 MP4 文件,則標識為:mp4:6721_75994f9.mp4。

startNumber: 這個字段其實有點意思。它可以分為 3 類來講解:-2,-1,>=0。

-2: 如果是該標識符,服務端會首先尋找是否有對應的 liveStream。沒有的話,就找 record_stream。如果還沒有的,這次請求會暫時掛起,直到獲取到下一次 live_stream。

-1: 只有 live_stream 才會播放。

=0: 相當于就是 seek video。它會直接找到 record_stream,并且根據該字段的值來確定播放開始時間。如果沒有的話,則播放 list 中的下一個 video。

durationNumber: 用來設置播放時長的。它里面也有幾個參數需要講解一下,-1,0,>0。

-1: 會一直播放到 live_stream 或者 record_stream 結束。

0: 會播放一段一段的 frame。一般不用。

0: 會直接播放指定 duration 之內的流。如果超出,則會播放指定時間段內容的 record_stream。

reset[Boolean]: 該字段沒啥用,一般可以忽略。用來表示否是拋棄掉前面的 playlist。

整個 play 包內容就已經介紹完了。我們可以看看實際的 play 抓包結果:

那 play 包是在那個環節發送,發送完之后需不需要對應的 _result 包呢?

play 包比較特殊,它是不需要 _result 回包的。因為,一旦 play 包成功接收后。server 端會直接開始進行 streamBegin 的操作。

整個流程為:

到這里,后續就可以開始正式接收 video 和 audio 的 stream。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84682.html

相關文章

發表評論

0條評論

李世贊

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<