RTMP協議是一個互聯網五層體系結構中應用層的協議。RTMP協議中基本的數據單元稱爲消息(Message)。當RTMP協議在互聯網中傳輸數據的時候,消息會被拆分成更小的單元,稱爲塊(Chunk)。
一.定義
Payload(載荷):包含於一個數據包中的數據,例如音頻採樣或者視頻壓縮數據。
Packet(數據包):一個數據包由固定頭和載荷數據構成。一些底層協議可能會要求對數據包進行封裝。
Port(端口):TCP/IP使用小的正整數對端口進行標識。OSI傳輸層使用的運輸選擇器 (TSEL) 相當於端口。
Transport address(傳輸地址):用以識別傳輸層端點的網絡地址和端口的組合,例如一個IP地址和一個TCP端口。
Message stream(消息流):通信中消息流通的一個邏輯通道。
Message stream ID(消息流ID):每個消息有一個關聯的ID,使用ID可以識別出該消息屬於哪個消息流。
Chunk(塊):消息的一段。消息在網絡發送之前被拆分成很多小的部分。塊按照時間戳的順序進行端到端的傳輸。
Chunk stream(塊流):通信中允許塊流向一個特定方向的邏輯通道。塊流可以從客戶端流向服務器,也可以從服務器流向客戶端。
Chunk stream ID(塊流 ID):每個塊有一個關聯的ID,使用ID可以識別出該塊屬於哪個塊流。
Multiplexing(合成):將獨立的音頻/視頻數據合成爲一個連續的音頻/視頻流,這樣就可以同時發送視頻和音頻了。
DeMultiplexing(分解):Multiplexing 的逆向處理,將交叉的音頻和視頻數據還原成原始音頻和視頻數據的格式。
Remote Procedure Call(RPC 遠程方法調用):允許客戶端或服務器調用對端的一個子程序或者程序的請求。
Metadata(元數據):關於數據的描述。比如電影的 metadata 包括電影標題、持續時間、創建時間等等。
Application Instance (應用實例):應用實例運行於服務器上,客戶端可連接這個實例併發送連接請求,連接服務器。
Action Message Format (AMF,操作消息格式):AMF是Adobe獨家開發出來的通信協議,它採用二進制壓縮,序列化、反序列化、傳輸數據,從而爲Flash 播放器與Flash Remoting網關通信提供了一種輕量級的、高效能的通信方式。如下圖所示。
AMF的初衷只是爲了支持Flash ActionScript的數據類型,目前有兩個版本:AMF0和AMF3。AMF從Flash MX時代的AMF0發展到現在的AMF3。AMF3用作Flash Playe 9的ActionScript 3.0的默認序列化格式,而AMF0則用作舊版的ActionScript 1.0和2.0的序列化格式。在網絡傳輸數據方面,AMF3比AMF0更有效率。AMF3能將int和uint對象作爲整數(integer)傳輸,並且能序列化 ActionScript 3.0才支持的數據類型, 比如ByteArray,XML和Iexternalizable。
二.握手
握手以客戶端發送C0和C1塊開始。
客戶端必須等待接收到S1才能發送C2。
客戶端必須等待接收到S2才能發送任何其他數據。
服務器端必須等待接收到C0才能發送S0和S1,也可以等待接收到C1再發送S0和S1。服務器端必須等待接收到C1才能發送S2。服務器端必須等待接收到C2才能發送任何其他數據。
1.C0和S0格式
C0和S0都是八位,即一個字節,如下所示:
Version(8bits):在C0中,它表示客戶端的RTMP版本;在S0中,它表示服務器端的RTMP版本。RTMP規範目前將它定義爲3。0—2用於早期的產品,已經被廢棄。4—31保留,用於RTMP未來版本。32—255禁止使用。
2.C1和S1格式
C1和S1都是1536個字節,如下所示:
time(4字節):包含時間戳,該時間戳應該被用做本終端發送的塊的起點。該字段可以爲0或者其他任意值。
zero(4字節):該字段必須爲0。
random data(1528字節):該字段可以包含任意值。終端需要區分出是它發起的握手還是對端發起的握手,所以這該字段應該發送一些足夠隨機的數。
3.C2和S2格式
C2和S2也都是1536個字節,幾乎是C1和S1的副本,如下所示:
time(4字節):包含時間戳,必須與C1或S1中的時間戳相同。
time2(4字節):包含時間戳,必須與前一個C1或S1中的時間戳相同。
random echo(1528字節):該字段必須與S1或者S2中的隨機數相同。
4.握手示意圖三.塊
握手之後,連接開始對一個或多個塊流進行合併。每個塊都有一個唯一ID對其進行關聯,這個ID叫做chunk stream ID(塊流ID)。這些塊通過網絡進行傳輸,在發送端,每個塊必須被完全發送纔可以發送下一塊。在接收端,這些塊根據塊流ID被組裝成消息。
每個塊都是由塊頭和塊數據體組成,而塊頭自身也是由三部分組成,塊格式如下所示:
Basic Header(基本頭,1—3字節):該字段編碼了塊流ID和塊類型。塊類型決定了Message Header(消息頭)的編碼格式。該字段長度完全取決於塊流ID,因爲塊流ID是一個可變長度的字段。
Message Header(消息頭,0、3、7或11字節):該字段編碼了消息的相關信息,標識了塊數據所屬的消息。該字段的長度由塊類型決定。
Extended Timestamp(擴展時間戳,0或4字節):該字段只在特定情況下出現。
Chunk Data(塊數據,可變大小):塊的載荷部分,取決於配置的最大塊尺寸,一般爲128字節。
1.Basic Header
塊基本頭對塊類型(用fmt 字段表示,參見下圖) 和塊流ID(chunk stream ID)進行編碼。fmt字段佔2bits,取值範圍時0—3。RTMP協議最多支持65597個流,流的ID範圍是3—65599。ID值0、1和2被保留,0表示兩字節形式,1表示三字節形式,2的塊流ID被保留,用於下層協議控制消息和命令。
☆一字節形式
ID取值範圍3—63,0、1和2用於標識多字節形式。
☆兩字節形式
ID取值範圍64—319,即第二個字節+64。
☆三字節形式
ID取值範圍64—68899,即第三個字節*256+第二個字節+64。
2.Message Header
有四種類型的塊消息頭,由塊基本頭中的“fmt”字段決定。
☆類型0
由11個字節組成,必須用於塊流的起始塊或者流時間戳重置的時候。
timestamp(3字節):消息的絕對時間戳,如果大於等於16777215(0xFFFFFF),該字段仍爲16777215,此時Extend Timestamp(擴展時間戳)字段存在,用於對溢出值進行擴展。否則,該字段標識整個時間戳,不需要擴展。
message length(3字節):通常與塊載荷的長度不同,塊載荷長度通常表示塊的最大長度128字節(除了最後一個塊)和最後一個塊的剩餘空間。
message type id(1字節):消息類型。
message stream id(4字節):該字段用小端模式保存。
☆類型1
由7個字節組成,不包括message stream ID(消息流ID),此時塊與之前的塊取相同的消息流ID。可變長度消息的流(例如,一些視頻格式)應該在第一塊之後使用這一格式表示之後的每個新塊。
timestamp delta(3字節):前一個塊時間戳與當前塊時間戳的差值,即相對時間戳,如果大於等於16777215(0xFFFFFF),該字段仍爲16777215,此時Extend Timestamp(擴展時間戳)字段存在,用於對溢出值進行擴展。否則,該字段標識整個差值,不需要擴展。
message length(3字節):通常與塊載荷的長度不同,塊載荷長度通常表示塊的最大長度(除了最後一個塊)和最後一個塊的剩餘空間。該長度是指爲塊載荷AMF編碼後的長度。
message type id(1字節):消息類型。
☆類型2
由3個字節組成,既不包括message stream ID(消息流ID),也不包括message length(消息長度),此時塊與之前的塊取相同的消息流ID和消息長度。固定長度消息的流(例如,一些音頻格式)應該在第一塊之後使用這一格式表示之後的每個新塊。
timestamp delta(3字節):前一個塊時間戳與當前塊時間戳的差值,如果大於等於16777215(0xFFFFFF),該字段仍爲16777215,此時Extend Timestamp(擴展時間戳)字段存在,用於對溢出值進行擴展。否則,該字段標識整個差值,不需要擴展。
☆類型3
沒有消息頭,從之前具有相同塊流ID的塊中取相應的值。當一條消息被分割成多個塊時,所有的塊(除了第一個塊)應該使用這種類型。
3.Extended timestamp(3字節)
只有當塊消息頭中的普通時間戳設置爲0x00ffffff時,本字段才被傳送。如果普通時間戳的值小於0x00ffffff,那麼本字段一定不能出現。如果普通時間戳字段不出現本字段也一定不能出現。
4.例子
☆不分割消息
從上面兩個表中可以看出,從消息3開始,數據處理得到優化,此時Chunk除了載荷以外,只多了一個塊基本頭。
☆分割消息
當消息的載荷長度超過128字節時,需要將消息分割爲若干個塊進行傳輸。
從上面兩個例子可以看出,塊類型3有兩種用法。一個是指定一個新消息的消息頭可以派生自已經存在的狀態數據(例一),另一個是指定消息的連續性(例二)。
四.消息
消息是RTMP協議中基本的數據單元。不同種類的消息包含不同的Message Type ID,代表不同的功能。RTMP協議中一共規定了十多種消息類型,分別發揮着不同的作用。例如,Message Type ID在1-7的消息用於協議控制,這些消息一般是RTMP協議自身管理要使用的消息,用戶一般情況下無需操作其中的數據。Message Type ID爲8,9的消息分別用於傳輸音頻和視頻數據。Message Type ID爲15-20的消息用於發送AMF編碼的命令,負責用戶與服務器之間的交互,比如播放,暫停等等。
1.消息頭
消息頭(Message Header)有四部分組成:標誌消息類型的Message Type ID,標誌載荷長度的Payload Length,標識時間戳的Timestamp,標識消息所屬媒體流的Stream ID。消息的格式如下所示。
2.載荷
載荷是消息包含的實際數據,它們可能是音頻採樣數據或者是視頻壓縮數據。
由於端與端之間實際傳輸的是塊,所以只需要將載荷加上塊頭封裝成塊。實際應用中,無擴展時間戳,一字節形式的塊基本頭就能滿足要求,整個塊頭滿足以下四種長度:
fmt=0:Basic Head+Message Head=1+11=12
fmt=1:Basic Head+Message Head=1+7=8
fmt=2:Basic Head+Message Head=1+3=4
fmt=3:Basic Head+Message Head=1+0=1
需要注意的是,當載荷爲H.264數據時,要使用AMF3進行編碼(即序列化),關於AMF3可以參考:AMF3中文版
五.打包H.264
如果整個打包過程都自己弄,是非常繁瑣的,還好網上有大神開源了RTMP庫,這裏使用librtmp進行H.264數據的打包推送。
librtmp的編譯可以參考:Win7(Windows 7)下用VS2012(Visual Studio 2012)編譯librtmp
使用librtmp時,解析RTMP地址、握手、建立流媒體鏈接和AMF編碼這塊我們都不需要關心,但是數據是如何打包並通過int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue) 函數推送的還是得學習一下。
RTMPPacket類型的結構體定義如下,一個RTMPPacket對應RTMP協議規範裏面的一個塊(Chunk)。
- typedef struct RTMPPacket
- {
- uint8_t m_headerType;//塊消息頭的類型(4種)
- uint8_t m_packetType;//消息類型ID(1-7協議控制;8,9音視頻;10以後爲AMF編碼消息)
- uint8_t m_hasAbsTimestamp; //時間戳是絕對值還是相對值
- int m_nChannel; //塊流ID
- uint32_t m_nTimeStamp; //時間戳
- int32_t m_nInfoField2; //last 4 bytes in a long header,消息流ID
- uint32_t m_nBodySize; //消息載荷大小
- uint32_t m_nBytesRead; //暫時沒用到
- RTMPChunk *m_chunk; //<span style="font-family: Arial, Helvetica, sans-serif;">暫時沒用到</span>
- char *m_body; //消息載荷,可分割爲多個塊載荷
- } RTMPPacket;
- #define RTMP_DEFAULT_CHUNKSIZE 128//默認塊大小
- #define RTMP_BUFFER_CACHE_SIZE (16*1024)//開闢16K字節空間
- #define RTMP_PACKET_TYPE_AUDIO 0x08//音頻的消息類型
- #define RTMP_PACKET_TYPE_VIDEO 0x09//視頻的消息類型
- #define RTMP_MAX_HEADER_SIZE 18//塊基本頭+塊消息頭+擴展時間戳=3+11+4=18
- #define RTMP_PACKET_SIZE_LARGE 0//塊消息頭類型0
- #define RTMP_PACKET_SIZE_MEDIUM 1//塊消息頭類型1
- #define RTMP_PACKET_SIZE_SMALL 2//塊消息頭類型2
- #define RTMP_PACKET_SIZE_MINIMUM 3//塊消息頭類型3
RTMP_SendPacket函數
- //queue:TRUE爲放進發送隊列,FALSE是不放進發送隊列,直接發送
- int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)
- {
- const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
- uint32_t last = 0;//上一個塊的時間戳
- int nSize;//消息載荷大小,可分割爲多個塊載荷大小
- int hSize;//塊頭大小
- int cSize;//塊基本頭大小增量
- char *header;//指向塊頭起始位置
- char *hptr;
- char *hend;//指向塊頭結束位置
- char hbuf[RTMP_MAX_HEADER_SIZE];
- char c;
- uint32_t t;//相對時間戳
- char *buffer;//指向消息載荷
- char *tbuf = NULL;
- char *toff = NULL;
- int nChunkSize;//塊載荷大小
- int tlen;
- //不是完整塊消息頭(即不是11字節的塊消息頭)
- if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
- {
- //前一個塊和這個塊對比
- //原理參考 例子—不分割消息
- if (prevPacket->m_nBodySize == packet->m_nBodySize
- && prevPacket->m_packetType == packet->m_packetType
- && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)
- packet->m_headerType = RTMP_PACKET_SIZE_SMALL;
- //原理參考 例子—分割消息
- if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp
- && packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
- packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;
- //上一個塊的時間戳
- last = prevPacket->m_nTimeStamp;
- }
- //非法
- if (packet->m_headerType > 3)
- {
- RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.",
- (unsigned char)packet->m_headerType);
- return FALSE;
- }
- //nSize暫時設置爲塊頭大小;packetSize[] = { 12, 8, 4, 1 }
- nSize = packetSize[packet->m_headerType];
- //塊頭大小初始化
- hSize = nSize;
- cSize = 0;
- //相對時間戳,當塊時間戳與上一個塊時間戳的差值
- t = packet->m_nTimeStamp - last;
- if (packet->m_body)
- {
- //m_body是指向載荷數據首地址的指針,“-”號用於指針前移
- //header:塊頭起始位置
- header = packet->m_body - nSize;
- //hend:塊頭結束位置
- hend = packet->m_body;
- }
- else
- {
- header = hbuf + 6;
- hend = hbuf + sizeof(hbuf);
- }
- //當塊流ID大於319時
- if (packet->m_nChannel > 319)
- //塊基本頭是3個字節
- cSize = 2;
- //當塊流ID大於63時
- else if (packet->m_nChannel > 63)
- //塊基本頭是2個字節
- cSize = 1;
- if (cSize)
- {
- //header指針指塊頭起始位置,“-”號用於指針前移
- header -= cSize;
- //當cSize不爲0時,塊頭需要進行擴展,默認的塊基本頭爲1字節,但是也可能是2字節或3字節
- hSize += cSize;
- }
- //如果塊消息頭存在,且相對時間戳大於0xffffff,此時需要使用ExtendTimeStamp
- if (nSize > 1 && t >= 0xffffff)
- {
- header -= 4;
- hSize += 4;
- }
- hptr = header;
- //把塊基本頭的fmt類型左移6位。
- c = packet->m_headerType << 6;
- switch (cSize)
- {
- //把塊基本頭的低6位設置成塊流ID
- case 0:
- c |= packet->m_nChannel;
- break;
- //同理,但低6位設置成000000
- case 1:
- break;
- //同理,但低6位設置成000001
- case 2:
- c |= 1;
- break;
- }
- //可以拆分成兩句*hptr=c;hptr++,此時hptr指向第2個字節
- *hptr++ = c;
- //cSize>0,即塊基本頭大於1字節
- if (cSize)
- {
- //將要放到第2字節的內容tmp
- int tmp = packet->m_nChannel - 64;
- //獲取低位存儲於第2字節
- *hptr++ = tmp & 0xff;
- //塊基本頭是最大的3字節時
- if (cSize == 2)
- //獲取高位存儲於第三個字節(注意:排序使用大端序列,和主機相反)
- *hptr++ = tmp >> 8;
- }
- //塊消息頭一共有4種,包含的字段數不同,nSize>1,塊消息頭存在。
- if (nSize > 1)
- {
- //塊消息頭的最開始三個字節爲時間戳,返回值hptr=hptr+3
- hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
- }
- //如果塊消息頭包括MessageLength+MessageTypeID(4字節)
- if (nSize > 4)
- {
- //消息長度,爲消息載荷AMF編碼後的長度
- hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);
- //消息類型ID
- *hptr++ = packet->m_packetType;
- }
- //消息流ID(4字節)
- if (nSize > 8)
- hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
- //如果塊消息頭存在,且相對時間戳大於0xffffff,此時需要使用ExtendTimeStamp
- if (nSize > 1 && t >= 0xffffff)
- hptr = AMF_EncodeInt32(hptr, hend, t);
- //消息載荷大小
- nSize = packet->m_nBodySize;
- //消息載荷指針
- buffer = packet->m_body;
- //塊大小,默認128字節
- nChunkSize = r->m_outChunkSize;
- RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket,
- nSize);
- //使用HTTP
- if (r->Link.protocol & RTMP_FEATURE_HTTP)
- {
- //nSize:消息載荷大小;nChunkSize:塊載荷大小
- //例nSize:307,nChunkSize:128;
- //可分爲(307+128-1)/128=3個
- //爲什麼減1?因爲除法會只取整數部分!
- int chunks = (nSize+nChunkSize-1) / nChunkSize;
- //如果塊的個數超過一個
- if (chunks > 1)
- {
- //消息分n塊後總的開銷:
- //n個塊基本頭,1個塊消息頭,1個消息載荷,這裏是沒有擴展時間戳的情況
- //實際中只有第一個塊是完整的,剩下的只有塊基本頭
- tlen = chunks * (cSize + 1) + nSize + hSize;//這裏其實多算了一個塊基本頭
- //分配內存
- tbuf = (char *) malloc(tlen);
- if (!tbuf)
- return FALSE;
- toff = tbuf;
- }
- }
- while (nSize + hSize)
- {
- int wrote;
- //消息載荷小於塊載荷(不用分塊)
- if (nSize < nChunkSize)
- nChunkSize = nSize;
- RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)header, hSize);
- RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)buffer, nChunkSize);
- if (tbuf)
- {
- memcpy(toff, header, nChunkSize + hSize);
- toff += nChunkSize + hSize;
- }
- else
- {
- wrote = WriteN(r, header, nChunkSize + hSize);
- if (!wrote)
- return FALSE;
- }
- //消息載荷長度塊載荷長度
- nSize -= nChunkSize;
- //Buffer指針前移1個塊載荷長度
- buffer += nChunkSize;
- hSize = 0;
- //如果消息沒有發完
- if (nSize > 0)
- {
- header = buffer - 1;
- hSize = 1;
- if (cSize)
- {
- header -= cSize;
- hSize += cSize;
- }
- //塊基本頭第1個字節
- *header = (0xc0 | c);
- //如果塊基本頭大於1字節
- if (cSize)
- {
- int tmp = packet->m_nChannel - 64;
- header[1] = tmp & 0xff;
- if (cSize == 2)
- header[2] = tmp >> 8;
- }
- }
- }
- if (tbuf)
- {
- int wrote = WriteN(r, tbuf, toff-tbuf);
- free(tbuf);
- tbuf = NULL;
- if (!wrote)
- return FALSE;
- }
- /* we invoked a remote method */
- if (packet->m_packetType == 0x14)
- {
- AVal method;
- char *ptr;
- ptr = packet->m_body + 1;
- AMF_DecodeString(ptr, &method);
- RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);
- /* keep it in call queue till result arrives */
- if (queue)
- {
- int txn;
- ptr += 3 + method.av_len;
- txn = (int)AMF_DecodeNumber(ptr);
- AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);
- }
- }
- if (!r->m_vecChannelsOut[packet->m_nChannel])
- r->m_vecChannelsOut[packet->m_nChannel] = (RTMPPacket *) malloc(sizeof(RTMPPacket));
- memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
- return TRUE;
- }
現在要解決的是如何給結構體RTMPPacket中的消息載荷m_body賦值,即如何將H.264的NALU打包進消息載荷。
1.sps和pps的打包
sps和pps是需要在其他NALU之前打包推送給服務器。由於RTMP推送的音視頻流的封裝形式和FLV格式相似,向FMS等流媒體服務器推送H264和AAC直播流時,需要首先發送"AVC sequence header"和"AAC sequence header"(這兩項數據包含的是重要的編碼信息,沒有它們,解碼器將無法解碼),因此這裏的"AVC sequence header"就是用來打包sps和pps的。
AVC sequence header其實就是AVCDecoderConfigurationRecord結構,該結構在標準文檔“ISO/IEC-14496-15:2004”的5.2.4.1章節中有詳細說明,如下所示:
用表格表示如下:
FLV 是一個二進制文件,簡單來說,其是由一個文件頭(FLV header)和很多 tag 組成(FLV body)。tag 又可以分成三類: audio, video, script,分別代表音頻流,視頻流,腳本流,而每個 tag 又由 tag header 和 tag data 組成。
然後參照“Video File Format Specification Version 10”中The FLV File Format的Video tags章節,如下所示:
上表中tag header爲兩個4bits,即一個字節,其他的是tag data。inter frame即P frame。
AVC時,3字節CompositionTime無意義,通常設置爲0。
AVCDecoderConfigurationRecord結構的表格中可以看出,由於NALUnitLength-1=3,因此每個NALU包都有NALUnitLength=4個字節來描述它的長度。這4個字節需要添加到每個NALU的前面,因此上表中Data的結構實際上如下所示:
一個典型的打包示例如下所示:
- body = (unsigned char *)packet->m_body;
- i = 0;
- body[i++] = 0x17;// 1:Iframe 7:AVC ,元數據當做keyframe發送</span>
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00;
- //AVCDecoderConfigurationRecord
- body[i++] = 0x01;
- body[i++] = sps[1];
- body[i++] = sps[2];
- body[i++] = sps[3];
- body[i++] = 0xff;
- /*sps*/
- body[i++] = 0xe1;
- body[i++] = (sps_len >> 8) & 0xff;
- body[i++] = sps_len & 0xff;
- memcpy(&body[i],sps,sps_len);
- i += sps_len;
- /*pps*/
- body[i++] = 0x01;
- body[i++] = (pps_len >> 8) & 0xff;
- body[i++] = (pps_len) & 0xff;
- memcpy(&body[i],pps,pps_len);
- i += pps_len;
一個典型的打包示例如下所示:
- int i = 0;
- if(bIsKeyFrame)
- {
- body[i++] = 0x17;// 1:Iframe 7:AVC
- body[i++] = 0x01;// AVC NALU
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00;
- // NALU size
- body[i++] = size>>24 &0xff;
- body[i++] = size>>16 &0xff;
- body[i++] = size>>8 &0xff;
- body[i++] = size&0xff;
- // NALU data
- memcpy(&body[i],data,size);
- }
- else
- {
- body[i++] = 0x27;// 2:Pframe 7:AVC
- body[i++] = 0x01;// AVC NALU
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00;
- // NALU size
- body[i++] = size>>24 &0xff;
- body[i++] = size>>16 &0xff;
- body[i++] = size>>8 &0xff;
- body[i++] = size&0xff;
- // NALU data
- memcpy(&body[i],data,size);
- }
一個具體的例子:Qt基於librtmp推送H.264
參考鏈接:
http://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf