成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

RTMP H5 直播流技術(shù)解析

李世贊 / 2029人閱讀

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

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

RTMP 是什么

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

RTMP 握手

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

(C 代表 Client,S 代表 Server)

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

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

server: 服務(wù)端也需要發(fā)同樣 3 個包。 S0,S1,S2。

整個過程如上圖所述,但實際上有些細(xì)節(jié)需要注意。

握手開始:

【1】 客戶端發(fā)送 C0,C1 包

此時,客戶端處于等待狀態(tài)??蛻舳擞袃蓚€限制:

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

客戶端在未接收到 S2 之前不能發(fā)送任何實際數(shù)據(jù)包

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

此時,服務(wù)端處于等待狀態(tài)。服務(wù)端有兩個限制:

服務(wù)端在未接受到 C1 之前不能發(fā)送 S2.

服務(wù)端在未接收到 C2 之前不能發(fā)送任何實際數(shù)據(jù)包

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

【4】服務(wù)端接受到 C2 包后,返回 S2 包,并且此時握手已經(jīng)完成。

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

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

C0+C1

S0+S1+S2

C2

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

C0 && S0

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

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

S1:服務(wù)端返回其所支持的版本號。如果沒有客戶端的版本號,默認(rèn)返回 3。

C1 && S1

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

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

zero: 保留值 0.

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

C2 && S2

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

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

time2: C1/S1 發(fā)送的時間戳。

random: S1/C1 發(fā)送的隨機數(shù)。長度為 1528B。

這里需要提及的是,RTMP 默認(rèn)都是使用 Big-Endian 進行寫入和讀取,除非強調(diào)對某個字段使用 Little-Endian 字節(jié)序。

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

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 包。根據(jù)上面的字節(jié)包圖,可以清楚的知道包里面的詳細(xì)內(nèi)容。這里,為了簡單起見,我們排除其他協(xié)議的包頭,只是針對 RTMP 里面的包。而且,我們針對的只有 3 種包,S0/1/2。為了達到這種目的,我們需要在 data 時間中,加上相應(yīng)的鉤子才行。

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

Buffer 操作

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

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

比如,C1 中的 time 和 random,其實并不是必須字段,所以,為了簡單起見,我們可以默認(rèn)設(shè)為 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();
    }
}

接下來,我們來看一下,結(jié)合 server 完成的 RTMP 客戶端服務(wù)。

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);
    }
})

詳細(xì)代碼可以參考 gist。

RTMP 基本架構(gòu)

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

在 Message 下的 3 個一級子 Item 就是我們現(xiàn)在將要大致講解的內(nèi)容。

可以看到上面所有的 item 都有一個共同的父 Item--Message。它的基本結(jié)構(gòu)為:

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

Body: Body 內(nèi)容就是相應(yīng)發(fā)送的數(shù)據(jù)。這個根據(jù)不同的 typeID 來說,格式就完全不一樣了。

下面,我們先了解一下 Header 和不同 typeID 的內(nèi)容:

Header

RTMP 中的 Header 分為 Basic Header 和 Message Header。需要注意,他們兩者并不是獨立的,而是相互聯(lián)系。Message Header 的結(jié)構(gòu)由 Basic Header 的內(nèi)容來決定。

接下來,先分開來講解:

Basic Header

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

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

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

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

CS ID: 0 ==> 整個 BH 長為 2B,其中可以表示的 Stream ID 數(shù)量為 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 數(shù)量為 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          |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

當(dāng)然,后面 CS ID 的計算方法也是最后的結(jié)果加上 64。

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

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

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

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

Message Header

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

fmt: 0

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

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

timestamp 是絕對時間戳。用來代表當(dāng)前流編碼。

message length: 3B, 發(fā)送 message 的長度。

type id: 1B

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

fmt: 1

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

fmt: 2

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

fmt: 3

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

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

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

接下來的就是 Message Body 部分。

Message Body

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

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

PCM

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

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

message stream ID 為 0

chunk stream ID 為 2

如圖所示:

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

Set Chunk Size(1)

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

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

0: 只能設(shè)為 0 ,用來表示當(dāng)前的 PCM 的類型。

chunk size: 用來表示后面發(fā)送正式數(shù)據(jù)時的大小。范圍為 1-16777215。

如下,提供過 wireshark 抓包的結(jié)果:

Abort Message(2)

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

基本格式為:

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

Acknowledgement(3)

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

它基本格式為:

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

不過,該包在實際應(yīng)用中,沒有多高的出現(xiàn)頻率。

Window Acknowledgement Size(5)

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

詳細(xì)格式為:

通過,wireshark 抓包的結(jié)果為:

Set Peer Bandwidth(6)

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

基本格式為:

其中 Limit Type 有 3 個取值:

0: Hard,表示當(dāng)前帶寬需要和當(dāng)前設(shè)置的 window size 匹配

1: Soft,將當(dāng)前寬帶設(shè)置為該信息定義的 window size,或者已經(jīng)生效的 window size。主要取決于誰的 window size 更小

2: Dynamic,如果前一個 Limit Type 為 Hard 那么,繼續(xù)使用 Hard 為基準(zhǔn),否則忽略該次協(xié)議信息。

實際抓包情況可以參考:

UCM

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

msg stream ID 為 0

chunk stream ID 為 2

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

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

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

Stream Begin: Event Type 為 0。它常常出現(xiàn)在,當(dāng)客戶端和服務(wù)端成功 connect 后發(fā)送。Event Data 為 4B,內(nèi)容是已經(jīng)可以正式用來傳輸數(shù)據(jù)的 Stream ID(實際沒啥用)。

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

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

OK 剩下就是 Command Msg 里面的內(nèi)容了。

Command Msg

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

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

Video Msg

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

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

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

因為,RTMP 只是一個傳輸工具,里面?zhèn)魇裁催€是由具體的流生成框架來決定的。所以,這里,我選擇了一個非常具有代表性的 RTMP 直播流來進行講解。

通過 wireshark 抓包,可以捕獲到以下的 RTMP 包數(shù)據(jù):

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

如果看 Tag 的話,他們傳輸?shù)亩际?VideoData Tag。先看一下 FLV VideoData Tag 的內(nèi)容:

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

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

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

這是 KeyFrame 的包,注意 Buffer 開頭的 17 數(shù)字。大家可以找到上面的 FrameType 對應(yīng)找一找,看結(jié)果是不是一致的:

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

Audio Tag

Aduio Tag 也是和 Video Tag 一樣的蜜汁數(shù)據(jù)。通過觀察 FLV Audio Tag 的內(nèi)容:

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

這也是所有的 Audio Msg 的內(nèi)容。

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

音視頻同步

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

以 Audio 為準(zhǔn),Video 同步 Audio

以 Video 為準(zhǔn),Audio 同步 Video

以外部時間戳為準(zhǔn),AV 同時同步

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

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

// 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 前面并相差不遠(yuǎn)。下面,我們回到 RTMP 內(nèi)容。來看看 Command Msg 里面的內(nèi)容。

Command Msg

Command Msg 是 RTMP 里面的一個主要信息傳遞工具。常常用在 RTMP 前期和后期處理。Command Msg 是通過 AMF 的格式進行傳輸?shù)模ㄆ鋵嵕褪穷愃?JSON 的二進制編碼規(guī)則)。Command Msg 主要分為 net connectnet stream 兩大塊。它的交流方式是雙向的,即,你發(fā)送一次 net connect 或者 stream 之后,另外一端都必須返回一個 _result 或者 _error 以表示收到信息。詳細(xì)結(jié)構(gòu)可以參考下圖:

后續(xù),我們分為兩塊進行講解:

netConnection

netStream

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

NetConnection

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

connect

connect 是客戶端向 Server 端發(fā)送播放請求的。里面的字段內(nèi)容有:

Command Name[String]: 默認(rèn)為 connect。表示信息名稱

Transaction ID[Number]: 默認(rèn)為 1。

Command Object: 鍵值對的形式存放相關(guān)信息。

Optional: 可選值。一般沒有

那,Command Object 里面又可以存放些什么內(nèi)容呢?

app[String]: 服務(wù)端連接應(yīng)用的名字。這個主要根據(jù)你的 RTMP 服務(wù)器設(shè)定來設(shè)置。比如:live

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

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

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

audioCodecs[Number]: 客戶端支持的音頻解碼。后續(xù)會介紹。默認(rèn)可以設(shè)置為 4071

videoCodecs[Number]: 客戶端支持的視頻解碼。有自定義的標(biāo)準(zhǔn)。默認(rèn)可以設(shè)置為 252

videoFunction[Number]: 表明在服務(wù)端上調(diào)用那種特別的視頻函數(shù)。默認(rèn)可以設(shè)置為 1

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

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

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

Transaction ID[Number]: 默認(rèn)為 1。

Command Object: 鍵值對的形式存放相關(guān)信息。

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

可以參考:

connect 包發(fā)送的位置,主要是在 RTMP 握手結(jié)束之后。如下:

call

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

Procedure Name[String]: 調(diào)用處理程序的名字。

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

Command Object: 鍵值對的形式存放相關(guān)信息。AMF0/3

Optional: 可選值。一般沒有

Command Object 里面的內(nèi)容主要是針對程序,設(shè)置相關(guān)的調(diào)用參數(shù)。因為內(nèi)容不固定,這里就不介紹了。

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

Command Name[String]: 根據(jù) call 中 Command Object 參數(shù)來決定的。

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

Command Object: 鍵值對的形式存放相關(guān)信息。AMF0/3

Response[Object]: 響應(yīng)的結(jié)果值

createStream

createStream 包只是用來告訴服務(wù)端,我們現(xiàn)在要創(chuàng)建一個 channel 開始進行流的交流了。格式和內(nèi)容都不復(fù)雜:

Procedure Name[String]: 調(diào)用處理程序的名字。

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

Command Object: 鍵值對的形式存放相關(guān)信息。AMF0/3

當(dāng)成功后,服務(wù)端會返回一個 _result 或者 _error 包來說明接收成功,詳細(xì)內(nèi)容為:

Command Name[String]: 根據(jù) call 中 Command Object 參數(shù)來決定的。

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

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

Stream ID: 返回的 stream ID 值。

它的返回值很隨意,參考抓包內(nèi)容:

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

NetStream Msg

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

play

play 包主要是用來告訴 Server 正式播放音視頻流。而且,由于 RTMP 天然是做多流分發(fā)的。如果遇到網(wǎng)絡(luò)出現(xiàn)相應(yīng)的波動,客戶端可以根據(jù)網(wǎng)絡(luò)條件多次調(diào)用 play 命令,來切換不同模式的流。

其基本格式為:

Command Name[String]: 根據(jù) call 中 Command Object 參數(shù)來決定的。

Transaction ID[Number]: 默認(rèn)為 0。也可以設(shè)置為其他值

Command Object: 不需要該字段,在該命令中,默認(rèn)設(shè)為 Null

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

StreamName: "6721_75994f92ce868a0cd3cc84600a97f75c"

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

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

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

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

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

-1: 只有 live_stream 才會播放。

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

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

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

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

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

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

整個 play 包內(nèi)容就已經(jīng)介紹完了。我們可以看看實際的 play 抓包結(jié)果:

那 play 包是在那個環(huán)節(jié)發(fā)送,發(fā)送完之后需不需要對應(yīng)的 _result 包呢?

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

整個流程為:

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84682.html

相關(guān)文章

發(fā)表評論

0條評論

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