1. H264 NALU結構
h264 NALU: 0x00 00 00 01 | nalu_type(1字節)| nalu_data (N 字節) | 0x00 00 00 01 | ...
起始碼(4字節) 類型 數據 下一個NALU起始碼
H264 NALU固定以 0x00 00 00 01爲起始,NALU_data部分不會出現這個起始碼;
在找到下一個起始碼之前,當前NALU數據長度不知;
NALU_type 1字節,定義爲:1比特禁止位 | 2比特 重要性指示位 | 5比特 類型
固定爲0 11重要 不能少 1-12 由h264使用
00不重要 可以丟棄
幾個常用Nalu_type:
0x67 (0 11 00111) SPS 非常重要 type = 7
0x68 (0 11 01000) PPS 非常重要 type = 8
0x65 (0 11 00101) IDR幀 關鍵幀 非常重要 type = 5
0x61 (0 11 00001) I幀 重要 type=1 非IDR的I幀 不大常見
0x41 (0 10 00001) P幀 重要 type = 1
0x01 (0 00 00001) B幀 不重要 type = 1
0x06 (0 00 00110) SEI 不重要 type = 6
2. FLV tag
前面講過FLV文件就是由無數個Tag組成的,Tag有Video Tag, Audio Tag和Script Tag.
A/V Tag裏面存儲的就是音視頻編碼數據,Script Tag裏面是一些碼流描述信息。
理論上來說,不解析Script tag也可以對A/V Tag完整解碼。tag的固定格式是:
Tag Type(1字節) | DataSize(3字節) | TimeStamp(3字節) | TimeStampExtended (1字節)| StreamID (3) | ...
下面將分別介紹各種NALU封到tag裏面的結構。
2. 一般Video tag
字節位置 意義
0x09, // 0, TagType
0xzz, 0xzz, 0xzz, // 1-3, DataSize,
0xzz, 0xzz, 0xzz, 0xzz, // 4-6, 7 TimeStamp | TimeStampExtend
0x00, 0x00, 0x00, // 8-10, StreamID
0xz7, // 11, FrameType | CodecID
0x01, // 12, AVCPacketType
0x00, 0x00, 0x00, // 13-15, CompositionTime
0xzz, 0xzz, 0xzz, 0xzz, // 16-19, NaluLength NBytes
0xzz, ...., ...., 0xzz, // NBytes, NaluData
0xzz, 0xzz, 0xzz, 0xzz. // N+1-N+3, PreviousTagSize
其中 0xzz的意思是該字節根據實際情況付不同的值
2.1 DataSize[1,2,3] = NaluLength + 5 + 4;
5 是 AVCPacket頭5比特,(FrameType+CodecID | AVCPacketType | CompositionTime)
4 是寫入NaluLength
[2015/12/31]對於多slice幀,多個slice依次寫入,結構都是 slice length(4字節) 類似上面16-19字節;+ slice data
DataSize[1,2,3] = 5 + 4*slice_number + slice_data0 + slice_dataN
int cts = 0;
TimeStamp[0,1,2] = cts[0,1,2];
TimeStampExtend[0] = cts[3];
cts += 40;
2.3 if(nalu_type == IDR) FrameType | CodecID = 0x17;
else FrameType | CodecID = 0x27;
2.4 NaluLength就是nalu長度,然後緊跟N字節的Nalu數據。
2.5 PreviousTagSize在這裏計算最爲方便,PreviousTagSize = 11 + 5 + 4 + NaluLength
11 是video tag頭數據 (TagType到StreamID)
IDR,I,P,B幀的NALU都是這個結構
3. SPS/PPS NALU
SPS和PPS在FLV裏面稱爲序列頭信息sequence header,它的AVCPacketType爲0x00
字節位置 意義
0x09, // 0, TagType
0xzz, 0xzz, 0xzz, // 1-3, DataSize,
0x00, 0x00, 0x00, 0x00, // 4-6, 7; TimeStamp | TimeStampExtend
0x00, 0x00, 0x00, // 8-10, StreamID
// AVC video tage header 5Bytes
0x17, // 11, FrameType | CodecID
0x00, // 12, AVCPacketType
0x00, 0x00, 0x00, // 13-15, CompositionTime
// AVCDecoderConfigurationRecord 6 Bytes
0x01, // 16, ConfigurationVersion
0xzz, // 17, AVC Profile SPS[1]
0x00, // 18, profile_compatibility SPS[2]
0xzz, // 19, AVC Level SPS[3]
0xFF, // 20, lengthSizeMinusOne,
// reserved 6bits | NAL unit length-1, commonly be 3
0xzz, // 21, numOfSequenceParameterSets,
// reserved 3bits | SPS count, commonly be 1
0xzz, 0xzz, // 22-23, SPS0 Length N0 Byte
0xzz, ...., 0xzz, // N0 Byte SPS0 Data
0xzz, 0xzz, // SPSm Length Nm Byte (如果存在) 循環存放最多31個SPS
0xzz, ...., 0xzz, // Nm Byte SPSm Data
0xzz, // PPS count
0xzz, 0xzz, // PPS0 Length
0xzz, ...., 0xzz, // N0 Byte PPS0 Data
0xzz, 0xzz, // PPSm Length Nm Byte (如果存在) 循環存放最多255個PPS
0xzz, ...., 0xzz, // Nm Byte PPSm Data
0xzz, 0xzz, 0xzz, 0xzz. // N+1-N+3, PreviousTagSize
3.1 在H.264碼流裏面reserved bit一般爲0; 而在FLV碼流裏面reserved bit定義爲1
3.2 在H.264裏面 SPS和PPS是對立的NALU,但是在FLV裏面會把他們統一寫在一個Video Tag裏面。
而且這個tag必須是FLV裏面第一個Video Tag,否則接收到其他video tag也沒法解碼.
爲了防止SPS,PPS數據丟失,有些編碼器會在每個IDR幀之前重複發SPS,PPS。這些SPS其實是一樣的。
但也不排除有些變態的編碼器前後的SPS會不同,畢竟標準容許這樣做。
這樣就需要首先遍歷一邊h264碼流,將其中不同的SPS,PPS提起出來,先記錄下來,然後再統一寫到FLV。
也可以大膽一點接收到第一個SPS和第一個PPS後就結束這個遍歷,就當作碼流裏面只有一個SPS和一個PPS。
3.3 DataSize=5 + // AVC video tag header (FrameType + CodecID | .. CompositionTime)
6 + // AVCDecoderConfigurationRecord
SPSCount*2 + // 每個SPS長度2字節
各個 SPSDataLength + // 所有SPS數據長度和
1 + // PPS個數
PPSCount*2 + // 每個PPS長度2字節
各個 PPSDataLength; // 所有PPS數據長度和
3.4 AVC Profile和 AVC Level就等於SPS NALU裏面第1字節和第3字節 (第0字節爲NaluType)
3.5 lengthSizeMinusOne,這個定義沒有理解,不知道低2比特是什麼含義,看到很多文檔裏面就直接設爲0b11, 所有這個字節爲 0xFF
3.6 numOfSequenceParameterSets, 低5比特是SPS個數,H.264標準裏面定義最多SPS個數爲255,這裏只有31。 不知道會不會存在問題,當然一般情況下就一個SPS,該值爲 0xE1 (0b111 00001)
3.7 每個SPS,PPS數據長度都用兩個字節來表述,
3.8 這個tag的 PreviousTagSize = 11 + DataSize。 11 是Video tag (TagType到StreamID)4. FLV頭
'F', 'L', 'V', // 0-2 FLV file Signature, also can be 'f''l''v'
0x01, // FLV version,
0x0z, // AV tag Enable. 0x05 AV both, 0x03 audio only, 0x01 video only
0x00, 0x00, 0x00, 0x09, // Length of this header.
0x00, 0x00, 0x00, 0x00. // PreviousTagLength.
5. SEI NALU
SEI是H.264裏面的附加增強信息NALU,他對解析解碼沒有幫助,但提供一些編碼器控制參數等信息。
FLV沒有一個Tag單獨包含SEI數據,它把SEI數據和緊隨其後那個視頻NALU數據打在同一個Video Tag裏面。
包含SEI數據的VideoTag結構如下
字節位置 意義
0x09, // 0, TagType
0xzz, 0xzz, 0xzz, // 1-3, DataSize,
0xzz, 0xzz, 0xzz, 0xzz, // 4-6, 7; TimeStamp | TimeStampExtend
0x00, 0x00, 0x00, // 8-10, StreamID
0x27, // 11, FrameType | CodecID
0x01, // 12, AVCPacketType
0x00, 0x00, 0x00, // 13-15, CompositionTime
0xzz, 0xzz, 0xzz, 0xzz, // 16-19, SEILength NBytes
0xzz, ...., ...., 0xzz, // NBytes, SEIData
0xzz, 0xzz, 0xzz, 0xzz, // NaluLength NBytes
0xzz, ...., ...., 0xzz, // NBytes, NaluData
0xzz, 0xzz, 0xzz, 0xzz. // PreviousTagSize
5.1 DataSize[0,1,2] = (NaluLength + 5 + 4) + (SEILength + 4);
6. 得到NALU代碼
// 輸入: H264_fp 264文件指針
// 輸出: 找到的Nalu長度,
*nalu_type返回找到的NALU類型
int h264_get_nalu(FILE *h264_fp, uint8_t *nalu_type) {
int start_pos = -1;
int nalu_size = 0;
int zero_num = 0;
uint8_t tmp;
while(!feof(h264_fp)){
fread(&tmp, 1, 1, h264_fp);
if(tmp == 0) zero_num++;
else if(tmp == 1) {
if(zero_num >= 3) {
if(start_pos == -1) {
start_pos = ftell(h264_fp);
fread(nalu_type, 1, 1, h264_fp);
} else {
nalu_size = ftell(h264_fp) - start_pos - 4;
fseek(h264_fp, start_pos, 0);
break;
}
}
} else
zero_num = 0;
}
return nalu_size;
}