1. 程式人生 > >RTMP協議以及提取RTMP視訊流組成H264視訊檔案

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, 8, 4,  1 byte(s).完整的RTMP包頭應該是12bytes,包含了時間戳,AMFSize,AMFType,StreamID資訊, 8位元組的包頭只紀錄了時間戳,AMFSize,AMFType,其他位元組的包頭紀錄資訊依次類推 。包體最大長度預設為128位元組,通過chunkSize可改變包體最大長度,通常當一段AFM資料超過128位元組後,超過128的部分就放到了其他的RTMP封包中,包頭為一個位元組.
完整的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

Head_Type
的後面6BitStreamID決定了ChannelID  StreamIDChannelID對應關係:StreamID=(ChannelID-4)/5+1 參考red5

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:
1 = key frame (for AVC, a seekable frame)
2 = inter frame (for AVC, a non-seekable frame)
3 = disposable inter frame (H.263 only)
4 = generated key frame (reserved for server use only)
5 = video info/command frame

CodecID

UB [4]

Codec Identifier. The following values are defined:
2 = Sorenson H.263
3 = Screen video
4 = On2 VP6
5 = On2 VP6 with alpha channel
6 = Screen video version 2
7 = AVC

AVCPacketType

IF CodecID == 7
UI8

The following values are defined:
0 = AVC sequence header
1 = AVC NALU
2 = AVC end of sequence (lower level NALU sequence ender is not required or supported)

CompositionTime

IF CodecID == 7
SI24

IF AVCPacketType == 1
Composition time offset
ELSE
0
See ISO 14496-12, 8.15.3 for an explanation of composition
times. The offset in an FLV file is always in milliseconds.

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= 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) {

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

}

}

});

備註: