RTMP視訊流格式解析
阿新 • • 發佈:2019-01-11
FLV 格式解析
簡述:
FLV是由一個FLV Header 和 若干tag(Video Tag, Audio Tag, Script Tag三種,分別代表視訊流,音訊流和指令碼流)組成的二進位制檔案。
FLV Header示意圖
FLV Header:
檔案型別: 固定為 "FLV" (3 bytes) 版本資訊: 一般為0x01 (1 byte) 流資訊: 0000 0101 此flv檔案包含視音訊, 0000 0001 此flv檔案包含視訊 0000 0100 包含音訊 (1 byte) 頭長度: FLV檔案頭長度,一般為 3+1+1+4=9 bytes (4bytes)
FLV Body:
Body由一系列pre tag length 和 tag組成。
+-----------------------------------------------------------------------+
| Pre Tag Length | Tag Header | Tag Data | .... | Pre Tag Length | ... |
+-----------------------------------------------------------------------+
Pre Tag Length: 前一個tag的長度 4 bytes
Tag Header: 1 + 3 + 3 +1 + 3 = 11 bytes
Tag:
tag header (11 bytes)
+----------------------------------------------------------------------------------------+
| Tag Type(1 byte) | Tag Data Length(3 bytes) | Timestap(3 bytes) | TimestapExt(1 byte ) |
+----------------------------------------------------------------------------------------+
| StreamID(3 bytes) |
+--------------------+
Tag Type: Tag 型別 1 byte
0x08 音訊
0x09 視訊
0x12 指令碼
Tag Data Length: Tag Data 長度 3 bytes
Timestamp: 時間戳(單位ms) 3 bytes
TimestampExt: 擴充套件時間戳 1 byte
StreamID: 流ID 總是0 3 bytes
tag data
tag data如果是音訊資料,第一個byte記錄audio資訊:
前4bits表示音訊格式(全部格式請看官方文件):
hex | comment |
---|---|
0 | 未壓縮 |
1 | ADPCM |
2 | MP3 |
4 | Nellymoser 16-kHz mono |
5 | Nellymoser 8-kHz mono |
10 | AAC |
下面兩個bits表示samplerate:
hex | comment |
---|---|
0 | 5.5KHz |
1 | 11kHz |
2 | 22kHz |
3 | 44kHz |
下面1bit表示取樣長度:
hex | comment |
---|---|
0 | snd8Bit |
1 | snd16Bit |
下面1bit表示型別:
hex | comment |
---|---|
0 | sndMomo |
1 | sndStereo |
之後是資料。
如果是視訊資料,第一個byte記錄video資訊:
hex | comment |
---|---|
1 | keyframe |
2 | inner frame |
3 | disposable inner frame (h.263 only) |
4 | generated keyframe |
後4bits表示解碼器ID:
hex | comment |
---|---|
2 | seronson h.263 |
3 | screen video |
4 | On2 VP6 |
5 | On2 VP6 with alpha channel |
6 | Screen video version 2 |
7 | AVC (h.264) |
00 00 00 00:前一個tag的長度,由於是第一個tag所以全為0.
12: 表示tag型別為指令碼
00 00 F6: 表示tag data長度為0xF6個位元組
00 00 00: 時間戳
00: 擴充套件時間戳
00 00 00: 流ID
Video Tag
Audio Tag
RTMP抓包的視訊流:
RTMP視訊流格式與flv很相似,就是video tag 和 audio tag的tag data一個接一個的傳送(不含tag header 和 pre tag length)。
audio /video資訊:1 位元組 ,這裡0x17 表示I幀 AVC
AVC packet type : 1位元組
0x00: AVC Sequence Header
0x01: AVC NALU
composition time : 3位元組, AVC時無意義,全為0
當AVC packet type為AVC Sequence Header時,接下來就是AVCDecoderConfigurationRecord的內容:
type | length(byte) | value | comment |
---|---|---|---|
configurationVersion | 1 | 0x01 | 版本 |
AVCProfileIndication | 1 | 0x4d | sps[1] |
profile_compatibility | 1 | 0x00 | sps[2] |
AVCLevelIndication | 1 | 0x2a | sps[3] |
lengthSizeMinusOne | 1 | 0xff | FLV中NALU包長資料所使用的位元組數,包長= (lengthSizeMinusOne & 3) + 1 |
numOfSequenceParameterSets | 1 | 0xe1 | SPS個數,通常為0xe1 個數= numOfSequenceParameterSets & 01F |
sequenceParameterSetLength | 2 | 0x0014 | SPS長度 |
sequenceParameterSetNALUnits | SPS內容 | ||
numOfPictureParameterSets | 1 | 0x01 | PPS個數,通常為0x01 |
pictureParameterSetLength | 2 | 0x0004 | PPS長度 |
pictureParameterSetNALUnits | PPS內容 |
當AVC packet type為AVC NALU(0x01)時:
audio / video 資訊:1位元組
AVC packet type:AVC NALU
後跟1個或多個NALU
composition time:3位元組,AVC時無意義,全為0
NALU length:(lengthSizeMinusOne & 3) + 1位元組 NALU長度
NALU Data:
NALU length:
NALU Data:
…
NALU:
H264 SPS 和PPS 資料結構
SPS:Sequence Parameter Set
PPS:Picture Parameter Set
有如下H264bitstream
/* h.264 bitstreams */
const uint8_t sps[] =
{0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2};
const uint8_t pps[] =
{0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80};
0x00 00 00 01 或者 0x 00 00 01 作為分隔符
SPS 2進位制:
u:unsigned bit
ue:指數哥倫布碼
Parameter Name | Type | Value | Comments |
---|---|---|---|
forbidden_zero_bit | u(1) | 0 | Despite being forbidden, it must be set to 0! |
nal_ref_idc | u(2) | 3 | 3 means it is “important” (this is an SPS) |
nal_unit_type | u(5) | 7 | Indicates this is a sequence parameter set |
profile_idc | u(8) | 66 | Baseline profile |
constraint_set0_flag | u(1) | 0 | We’re not going to honor constraints |
constraint_set1_flag | u(1) | 0 | We’re not going to honor constraints |
constraint_set2_flag | u(1) | 0 | We’re not going to honor constraints |
constraint_set3_flag | u(1) | 0 | We’re not going to honor constraints |
reserved_zero_4bits | u(4) | 0 | Better set them to zero |
level_idc | u(8) | 10 | Level 1, sec A.3.1 |
seq_parameter_set_id | ue(v) | 0 | We’ll just use id 0. |
log2_max_frame_num_minus4 | ue(v) | 0 | Let’s have as few frame numbers as possible |
pic_order_cnt_type | ue(v) | 0 | Keep things simple |
log2_max_pic_order_cnt_lsb_minus4 | ue(v) | 0 | Fewer is better. |
num_ref_frames | ue(v) | 0 | We will only send I slices |
gaps_in_frame_num_value_allowed_flag | u(1) | 0 | We will have no gaps |
pic_width_in_mbs_minus_1 | ue(v) | 7 | SQCIF is 8 macroblocks wide |
pic_height_in_map_units_minus_1 | ue(v) | 5 | SQCIF is 6 macroblocks high |
frame_mbs_only_flag | u(1) | 1 | We will not to field/frame encoding |
direct_8x8_inference_flag | u(1) | 0 | Used for B slices. We will not send B slices |
frame_cropping_flag | u(1) | 0 | We will not do frame cropping |
vui_prameters_present_flag | u(1) | 0 | We will not send VUI data |
rbsp_stop_one_bit | u(1) | 1 | Stop bit. I missed this at first and it caused me much trouble. |
貼幾段程式碼:將RTSP回撥的H264裸流轉換並通過RTMP協議傳送到客戶端.
// pData: H264裸流資料
// 將H264裸流轉換成適用於RTMP協議的流併發送
void CRTMP::_DataCallBack( LONG nChannel, char* pData, LONG nSize, RTP_HEAD* pHead, RTP_FRAME_TYPE FrameType )
{
char* p = pData;
char* pEnd = pData + nSize;
if (m_bNeedUpdateMetadata)
{ // 第一次收到I幀時,獲取sps pps並儲存
if (FrameType != RTP_FRAME_TYPE_I)
{
return;
}
Nalu sps, pps;
p = ReadOneNalu(pData, nSize, sps); ASSERT(p != NULL);
p = ReadOneNalu(p, pEnd - p, pps); ASSERT(p != NULL);
h264_decode_sps((BYTE*)sps.data, sps.size, m_nWitdh, m_nHeight, m_nFps);
m_MetaData.nSpsLen = sps.size;
m_MetaData.Sps = (unsigned char*)calloc(sps.size, 1); memcpy(m_MetaData.Sps, sps.data, sps.size);
m_MetaData.nPpsLen = pps.size;
m_MetaData.Pps = (unsigned char *)calloc(pps.size, 1); memcpy(m_MetaData.Pps, pps.data, pps.size);
m_MetaData.nWidth = m_nWitdh;
m_MetaData.nHeight = m_nHeight;
m_MetaData.nFrameRate = m_nFps;
m_bNeedUpdateMetadata = false;
}
unsigned int tick_gap = 1000 / m_MetaData.nFrameRate; // 1000ms/fps
Nalu idr;
while ((p = ReadOneNalu(p, pEnd - p, idr)) != NULL)
{
if (idr.type == 0x07 || idr.type == 0x08)
{ // 忽略重複的sps pps
continue;
}
if (!SendH264Packet(idr, m_tick))
{
cout << "SendH264Packet Failed" << WSAGetLastError() << endl;
}
}
m_tick += tick_gap; // 更新時間戳
}
// 從快取讀取一個nalu
// 返回指向下一個nalu起始位置的指標或者NULL
static char* ReadOneNalu(char* pBuf, int nSize, Nalu& NaluUnit)
{
char Sep1[3], Sep2[4];
Sep1[0] = (char)(0x1 >> 16 & 0xFF);
Sep1[1] = (char)(0x1 >> 8 & 0xFF);
Sep1[2] = (char)(0x1 & 0xFF);
Sep2[0] = (char)(0x1 >> 24 & 0xFF);
Sep2[1] = (char)(0x1 >> 16 & 0xFF);
Sep2[2] = (char)(0x1 >> 8 & 0xFF);
Sep2[3] = (char)(0x1 & 0xFF);
bool bFindHead = false;
bool bFindTail = false;
char* pStart = pBuf;
char* pEnd = pBuf + nSize;
_FIND:
while (pStart <= pEnd - 4)
{ // nalu 以00 00 01 或者00 00 00 01作為分隔符
// look for head
if (!bFindHead
&&(::memcmp(Sep1, pStart, 3) == 0))
{
pStart += 3;
NaluUnit.data = pStart;
NaluUnit.type = NaluUnit.data[0] & 0x1F;
bFindHead = true;
}
else if (!bFindHead
&& (::memcmp(Sep2, pStart, 4) == 0))
{
pStart += 4;
NaluUnit.data = pStart;
NaluUnit.type = NaluUnit.data[0] & 0x1F;
bFindHead = true;
}
// look for tail
if (bFindHead)
{
if (pEnd - pStart < 3)
{
break;
}
if (::memcmp(Sep1, pStart, 3) == 0)
{
NaluUnit.size = pStart - NaluUnit.data;
bFindTail = true;
break;
}
else if (::memcmp(Sep2, pStart, 4) == 0)
{
NaluUnit.size = pStart - NaluUnit.data;
bFindTail = true;
break;
}
}
++pStart;
}
if (!bFindTail)
{
NaluUnit.size = pEnd - NaluUnit.data;
}
if ((NaluUnit.type & 0x1F) == 0x06)
{ // sei 跳過
bFindHead = false;
bFindTail = false;
goto _FIND;
}
return bFindHead ? NaluUnit.data + NaluUnit.size : NULL;
}
// 將nalu封包併發送
// 大端法儲存
bool CRTMP::SendH264Packet(Nalu nalunit, unsigned int timestamp)
{
CAMFBuffer buf(nalunit.size + 9);
RTMP_Packet p;
if (nalunit.type == 5)
{
buf.WriteByte(0x17); // I 幀
buf.WriteByte(0x1); // nalu
buf.WriteInt24(0x0);
buf.WriteInt32(nalunit.size);
buf.WriteBuffer(nalunit.data, nalunit.size);
SendSpsPpsInfo(m_MetaData.Pps, m_MetaData.nPpsLen, m_MetaData.Sps, m_MetaData.nSpsLen);
}
else
{
buf.WriteByte(0x27); // p、b 幀
buf.WriteByte(0x1); // nalu
buf.WriteInt24(0x0);
buf.WriteInt32(nalunit.size);
buf.WriteBuffer(nalunit.data, nalunit.size);
}
return SendPacket(buf, 0x9, timestamp);
}
// 傳送sps,pps資訊
bool CRTMP::SendSpsPpsInfo(unsigned char *pps,int pps_len,unsigned char * sps,int sps_len)
{
CAMFBuffer buffer(pps_len + sps_len + 16);
buffer.WriteByte(0x17);
buffer.WriteByte(0x0); // AVCSequenceHeader
buffer.WriteInt24(0x0); // composition time
buffer.WriteByte(0x01);
buffer.WriteByte(sps[1]);
buffer.WriteByte(sps[2]);
buffer.WriteByte(sps[3]);
buffer.WriteByte((char)0xFF); // lengthSizeMinusOne NALU包長所用位元組數=(lengthSizeMinusOne & 3) + 1
buffer.WriteByte((char)0xE1); // numOfSequenceParameterSet SPS個數=numOfSequenceParameterSet & 0x1F
buffer.WriteInt16(sps_len); // SPS 長度
buffer.WriteBuffer((char*)sps, sps_len);
buffer.WriteByte((char)0x01); // numOfPictureParameterSet PPS個數=numOfPictureParameterSet & 0x1F
buffer.WriteInt16(pps_len); // PPS 長度
buffer.WriteBuffer((char*)pps, pps_len);
return SendPacket(buffer, 0x9, 0);
}