RTMP協議封裝H264格式詳解(RTMP協議封裝H264格式詳解)
2023-05-05 16:45:11 2
首先我們獲得h264的流,在監聽裡,我們通過參數可以獲得RTMP包 IStreamPacket,調用getData方法直接獲得包數據 放入IOBuffer。以下是提取並修改數據存成h264文件的步驟
1. 添加監聽 IStreamListener
2. 通過IOBuffer的put函數將每次獲得的包數據放入新的IObuffer
3. 在流結束時將IOBuffer存成文件
4. 用工具,如UltraEdit打開文件,查看裡面的數據並分析
5. 根據分析結果修改程序,提取h264視頻文件所需的數據並存儲
1.RTMP協議
RTMP協議封包由一個包頭和一個包體組成,包頭可以是4種長度的任意一種:12, 8, 4, 1 byte(s).完整的RTMP包頭應該是12bytes,包含了時間戳,AMFSize,AMFType,StreamID信息, 8位元組的包頭只紀錄了時間戳,AMFSize,AMFType,其他字節的包頭紀錄信息依次類推 。包體最大長度默認為128位元組,通過chunkSize可改變包體最大長度,通常當一段AFM數據超過128位元組後,超過128的部分就放到了其他的RTMP封包中,包頭為一個字節. 完整的12位元組RTMP包頭每個字節的含義:
1.1 Head_Type
第一個字節Head_Type的前兩個Bit決定了包頭的長度.它可以用掩碼0xC0進行"與"計算:
Head_Type的前兩個Bit和長度對應關係:
Head_Type的後面6個Bit和StreamID決定了ChannelID。 StreamID和ChannelID對應關係:StreamID=(ChannelID-4)/5 1 參考red5
例如在rtmp包裡面經常看到的0xC2,就表示一字節的包頭,channel=2.
1.2 TiMMER
TiMMER佔3個字節紀錄的是時間戳,音視頻流的時間戳是統一排的。可分為絕對時間戳和相對時間戳。 fms對於同一個流,發布的時間戳接受的時間戳是有區別的
publish時間戳,採用相對時間戳,時間戳值等於當前媒體包的絕對時間戳與上個媒體包的絕對時間戳之間的差距,也就是說音視頻時間戳在一個時間軸上面.單位毫秒。
play時間戳,相對時間戳,時間戳值等於當前媒體包的絕對時間戳與上個同類型媒體包的絕對時間戳之間的差距,也就是說音視頻時間戳分別為單獨的時間軸,單位毫秒。
FLV格式文件時間戳,絕對時間戳,時間戳長度3個字節。超過0xFFFFFF後時間戳值等於TimeStamp &0xFFFFFF。 flv格式文件影片總時間長度保存在onMetaData的duration屬性裡面,長度為8個字節,是一個翻轉的double類型。 1.3 AMFSize
AMFSize佔三個字節,這個長度是AMF長度,可超過RTMP包的最大長度128位元組。如果超過了128位元組,那麼由多個後續RTMP封包組合,每個後續RTMP封包的頭只佔一個字節。一般就是以0xC?開頭。 1.4 AMFType
AMFSize佔三個字節,這個長度是AMF長度,可超過RTMP包的最大長度128位元組。 AMFType是包的類型
1.6 StreamID
StreamID是音視頻流的ID,如果AMFType!=0x08或!=0x09那麼 StreamID為0。 ChannelID 和StreamID之間的計算公式:StreamID=(ChannelID-4)/5 1 參考red5 例如當ChannelID為2、3、4時StreamID都為1當ChannelID為9的時候StreamID為2
2.RTMP包的數據部分分析
如果 AMFType = 0×09, 數據就是 VIDEO Data
Video Data由多個video tag組成
一個video tag,包含的信息:SPS,PPS,訪問單元分隔符,SEI,I幀包
首先我們來看下vedio tag
如果TAG包中的TagType==9時,就表示這個TAG是video.
StreamID之後的數據就表示是VideoTagHeader,VideoTagHeader結構如下:
VideoTagHeader的頭1個字節,也就是接跟著StreamID的1個字節包含著視頻幀類型及視頻CodecID最基本信息.表裡列的十分清楚.
VideoTagHeader之後跟著的就是VIDEODATA數據了,也就是videopayload.當然就像音頻AAC一樣,這裡也有特例就是如果視頻的格式是AVC(H.264)的話,VideoTagHeader會多出4個字節的信息.
相關視頻推薦
音視頻開發進階-圖文並茂分析H264編碼原理
【音視頻開發】播放器-錄屏-音視頻同步問題剖析
學習地址:【免費】FFmpeg/WebRTC/RTMP/NDK/Android音視頻流媒體高級開發-學習視頻教程-騰訊課堂
需要更多ffmpeg/webrtc..音視頻流媒體開發學習資料加群812855908領取
AVCPacketType 和CompositionTime。AVCPacketType表示接下來 VIDEODATA(AVCVIDEOPACKET)的內容:
IF AVCPacketType ==0 AVCDecoderConfigurationRecord(AVC sequence header) IF AVCPacketType == 1 One or more NALUs (Full frames are required)
AVCDecoderConfigurationRecord.包含著是H.264解碼相關比較重要的sps和pps信息,再給AVC解碼器送數據流之前一定要把sps和pps信息送出,否則的話解碼器不能正常解碼。而且在解碼器stop之後再次start之前,如seek、快進快退狀態切換等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情況也是出現1次,也就是第一個 video tag.
2.1 AVC sequence header分析
§ 17:1-keyframe 7-avc
§ 00:AVC sequence header -- AVC packet type
§ 00 00 00:composition time,AVC時,全0,無意義
因為AVC packet type=AVCsequence header,接下來就是AVCDecoderConfigurationRecord的內容
§ configurationVersion= 01
§ AVCProfileIndication= 42
§ profile_compatibility=00
§ AVCLevelIndication =1E
§ lengthSizeMinusOne =FF -- FLV中NALU包長數據所使用的字節數,(lengthSizeMinusOne & 3) 1,實際測試時發現總為ff,計算結果為4,下文還會提到這個數據
§ numOfSequenceParameterSets= E1 -- SPS的個數,numOfSequenceParameterSets & 0x1F,實際測試時發現總為E1,計算結果為1
§ sequenceParameterSetLength= 0x2E-- SPS的長度,2個字節,計算結果46
§ sequenceParameterSetNALUnits=6742 80 1E 96 54 0A 0F D8 0A 84 00 00 03 00 04 00 00 03 00 7B 80 00 08 00 00 0400 1F c6 38 C0 00 04 00 0 03 02 00 0F E3 1C 3B 42 44 D4-- SPS,為剛才計算的46個字節,SPS中包含了視頻長、寬的信息
§ numOfPictureParameterSets= 01 -- PPS的個數,實際測試時發現總為E1,計算結果為1
§ pictureParameterSetLength= 0004-- PPS的長度
§ pictureParameterSetNALUnits=68ce 35 20 -- PPS
2.1 AVCNALU分析
接下來又是新的一包videotag數據了
§ 17:1-keyframe 7-avc
§ 01:AVC NALU
§ 00 00 00:composition time,AVC時,全0,無意義
因為AVCPacket type = AVCNALU,接下來就是一個或多個NALU
每個NALU包前面都有(lengthSizeMinusOne & 3) 1個字節的NAL包長度描述(前文提到的,還記得嗎),前面計算結果為4個字節
§ 00 00 00 02:2 -- NALU length
§ 09 10:NAL包
這裡插入一點NALU的小知識,每個NALU第一個字節的前5位標明的是該NAL包的類型,即NAL nal_unit_type
#define NALU_TYPE_SLICE 1
#define NALU_TYPE_DPA 2
#define NALU_TYPE_DPB 3
#define NALU_TYPE_DPC 4
#define NALU_TYPE_IDR 5
#define NALU_TYPE_SEI 6
#define NALU_TYPE_SPS 7
#define NALU_TYPE_PPS 8
#define NALU_TYPE_AUD 9//訪問分隔符
#define NALU_TYPE_EOSEQ 10
#define NALU_TYPE_EOSTREAM 11
#define NALU_TYPE_FILL 12
§ 09&0x1f=9,訪問單元分隔符
前面我們解析的sps頭字節為67,67&0x1f = 7,pps頭字節為68,68&0x1f=8,正好能對應上。
§ 00 00 00 29:說明接下來的NAL包長度為41
06 00 11 80 00 af c8 00 00 03 00 00 03 00 00 af c8 00 00 03 00 00 40 010c 00 00 03 00 00 03 00 90 80 08 00 00 03 00 0880:06&0x1f=6 -- SEI
§ 00 00 0F 9F:接下來的NAL包長度
65 88 80……:65&0x1f=5 -- I幀數據
這包video tag分析到此結束了,下面會緊接著來一些該I幀對應的P幀數據,
前面說的I幀數據從65 88 80,到下圖第一行的 5F 7E B0都是上一個video tag的內容,即前面說的65 88 80那個I幀的數據拉,27開始是新的一個video tag
§ 27:2-inter frame即P幀,7-codecid=AVC
§ 01:AVCPacket type = AVC NALU
§ 00 00 00:composition time,AVC時,全0,無意義
§ 00 00 00 02 09 30:跟上面分析的一樣拉,2個字節的nal包,訪問單元分隔符
§ 00 00 00 11:17位元組的NAL包
§ 06 01 0c 00 00 80 0000 90 80 18 00 00 03 00 08 80:06&0x1f=6 --SEI
§ 00 00 0C 45: NAL包數據長度
§ 41 9A 02……: 41&0x1f=1 --P幀數據
3.H264視頻文件格式
h264的NALU和NALU之間是由00 00 01(也可以是00 00 00 01)分隔開的,我們組成h264之後的格式為
1、00 00 00 01 SPS 0000 00 01 PPS 00 00 00 01訪問單元分隔符 00 00 00 01 SEI 0000 00 01 I幀 00 00 00 01 P幀 00 00 00 01 P幀……(P幀數量不定)
其中的訪問單元分隔符和SEI不是必須的
4.將獲得的包數據存儲成H264文件
通過以上我們清楚了H264文件的格式,也分析了現在獲得的數據格式,我們需要對這些數據進行處理,得到H264視頻要求的數據格式
1.當數據是AVC squence header(只有一次)的時候,提取sps,pps數據並加入 0000 01(也可以是00 00 00 01)隔開。
2. 當數據是AVC NALU時,四個字節存儲幀數據長度,後面緊跟著數據,根據長度計算幀數據長,提取數據,加上00 00 00 01,將每個幀數據隔開。
5.red5 數據處理代碼
@Override
public void streamPublishStart(IBroadcastStreamstream) {undefined
super.streamPublishStart(stream);
stream.addStreamListener(newIStreamListener {undefined
protected boolean bFirst = true;
@Override
public void packetReceived(IBroadcastStreamarg0, IStreamPacket arg1) {undefined
IoBufferin = arg1.getData;
if(arg1.getDataType == 0x09){
System.out.println("11111");
byte[] data = new byte[in.limit];
in.get(data);
byte[] foredata = { 0, 0, 0, 1 };
ioBuffer3.put(data);
// buflimit3 = in.limit;
if( bFirst) {undefined
//AVCsequence header
ioBuffer.put(foredata);
//獲取sps
intspsnum = data[10]&0x1f;
intnumber_sps = 11;
intcount_sps = 1;
while (count_sps<=spsnum){undefined
int spslen =(data[number_sps]&0x000000FF)<<8 |(data[number_sps 1]&0x000000FF);
number_sps = 2;
ioBuffer.put(data,number_sps, spslen);
ioBuffer.put(foredata);
number_sps = spslen;
count_sps ;
}
//獲取pps
intppsnum = data[number_sps]&0x1f;
intnumber_pps = number_sps 1;
intcount_pps = 1;
while (count_pps<=ppsnum){undefined
int ppslen =(data[number_pps]&0x000000FF)<<8|data[number_pps 1]&0x000000FF;
number_pps = 2;
ioBuffer.put(data,number_pps,ppslen);
ioBuffer.put(foredata);
number_pps = ppslen;
count_pps ;
}
bFirst =false;
} else {undefined
//AVCNALU
int len =0;
int num =5;
ioBuffer.put(foredata);
while(num<data.length) {undefined
len =(data[num]&0x000000FF)<<24|(data[num 1]&0x000000FF)<<16|(data[num 2]&0x000000FF)<<8|data[num 3]&0x000000FF;
num = num 4;
ioBuffer.put(data,num,len);
ioBuffer.put(foredata);
num = num len;
}
}
System.out.println("22222");
}else if (arg1.getDataType == 0x08) {undefined
// 這存儲處理音頻數據 Audio data
}
}
});
}
,