RTMP資料流提取RTMP視訊流組成H264視訊檔案
首先我們獲得h264的流,在監聽裡,我們通過引數可以獲得RTMP包 IStreamPacket,呼叫getData()方法直接獲得包資料 放入IOBuffer。以下是提取並修改資料存成h264檔案的步驟
1. 新增監聽 IStreamListener
2. 通過IOBuffer的put函式將每次獲得的包資料放入新的IObuffer
3. 在流結束時將IOBuffer存成檔案
4. 用工具,如UltraEdit開啟檔案,檢視裡面的資料並分析
5. 根據分析結果修改程式,提取h264視訊檔案所需的資料並存儲
1.RTMP協議
RTMP協議封包由一個包頭和一個包體組成,包頭可以是4種長度的任意一種
完整的12位元組RTMP包頭每個位元組的含義:
用途 |
大小(Byte) |
含義 |
Head_Type |
1 |
包頭 |
TiMMER |
3 |
時間戳 |
AMFSize |
3 |
資料大小 |
AMFType |
1 |
資料型別 |
StreamID |
4 |
流ID |
1.1 Head_Type第一個位元組Head_Type的前兩個Bit決定了包頭的長度.它可以用掩碼0xC0進行"與"計算:
Head_Type的前兩個Bit和長度對應關係:
Bits |
Header Length |
00 |
12 bytes |
01 |
8 bytes |
10 |
4 bytes |
11 |
1 byte |
ChannelID |
Use |
02 |
Ping 和ByteRead通道 |
03 |
Invoke通道 我們的connect() publish()和自字寫的NetConnection.Call() 資料都是在這個通道的 |
04 |
Audio和Vidio通道 |
05 06 07 |
伺服器保留,經觀察FMS2用這些Channel也用來發送音訊或視訊資料 |
例如在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 AMFTypeAMFSize佔三個位元組,這個長度是AMF長度,可超過RTMP包的最大長度128位元組。
AMFType是包的型別
0×01 |
Chunk Size |
changes the chunk size for packets |
0×02 |
Unknown |
|
0×03 |
Bytes Read |
send every x bytes read by both sides |
0×04 |
Ping |
ping is a stream control message, has subtypes |
0×05 |
Server BW |
the servers downstream bw |
0×06 |
Client BW |
the clients upstream bw |
0×07 |
Unknown |
|
0×08 |
Audio Data |
packet containing audio |
0×09 |
Video Data |
packet containing video data |
0x0A-0x0E |
Unknown |
|
0x0F |
FLEX_STREAM_SEND |
TYPE_FLEX_STREAM_SEND |
0x10 |
FLEX_SHARED_OBJECT |
TYPE_FLEX_SHARED_OBJECT |
0x11 |
FLEX_MESSAGE |
TYPE_FLEX_MESSAGE |
0×12 |
Notify |
an invoke which does not expect a reply |
0×13 |
Shared Object |
has subtypes |
0×14 |
Invoke |
like remoting call, used for stream actions too. |
0×16 |
StreamData |
這是FMS3出來後新增的資料型別,這種型別資料中包含AudioData和VideoData |
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結構如下:
Field |
Type |
Comment |
Frame Type |
UB [4] |
Type of video frame. The following values are defined: |
CodecID |
UB [4] |
Codec Identifier. The following values are defined: |
AVCPacketType |
IF CodecID == 7 |
The following values are defined: |
CompositionTime |
IF CodecID == 7 |
IF AVCPacketType == 1 |
VideoTagHeader的頭1個位元組,也就是接跟著StreamID的1個位元組包含著視訊幀型別及視訊CodecID最基本資訊.表裡列的十分清楚.
VideoTagHeader之後跟著的就是VIDEODATA資料了,也就是videopayload.當然就像音訊AAC一樣,這裡也有特例就是如果視訊的格式是AVC(H.264)的話,VideoTagHeader會多出4個位元組的資訊.
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= 002E-- 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) {
super.streamPublishStart(stream);
stream.addStreamListener(newIStreamListener() {
protected boolean bFirst = true;
@Override
public void packetReceived(IBroadcastStreamarg0, IStreamPacket arg1) {
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) {
//AVCsequence header
ioBuffer.put(foredata);
//獲取sps
intspsnum = data[10]&0x1f;
intnumber_sps = 11;
intcount_sps = 1;
while (count_sps<=spsnum){
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){
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 {
//AVCNALU
int len =0;
int num =5;
ioBuffer.put(foredata);
while(num<data.length) {
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) {
// 這儲存處理音訊資料 Audio data
}
}
});
}
備註: