一、H.264和FLV
H.264
H264是一个个NALU单元组成的,每个单元以00 00 01 或者 00 00 00 01分隔开来,每2个00 00 00 01之间就是一个NALU单元。我们实际上就是将一个个NALU单元封装进FLV文件。
每个NALU单元开头第一个byte的低5bits表示着该单元的类型,即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
每个NALU第一个byte & 0x1f 就可以得出它的类型,比如上图第一个NALU:67 & 0x1f = 7,则此单元是SPS,第三个:68 & 0x1f = 8,则此单元是PPS。
FLV
FLV(Flash Video)是Adobe公司设计开发的一种流行的流媒体格式,由于其视频文件体积轻巧、封装简单等特点,使其很适合在互联网上进行应用。此外,FLV可以使用Flash Player进行播放,而Flash Player插件已经安装在全世界绝大部分浏览器上,这使得通过网页播放FLV视频十分容易。目前主流的视频网站如优酷网,土豆网,乐视网等网站无一例外地使用了FLV格式。FLV封装格式的文件后缀通常为“.flv”。
二、FLV的结构
总体上看,FLV包括文件头(File Header)和文件体(File Body)两部分,其中文件体由一系列的Tag组成。因此一个FLV文件是如图1结构。
三、封装FLV
1、h264文件切割
-(void)splitH264FileFrom:(NSString *)path{
NSData *h264file = [NSData dataWithContentsOfFile:path];//H264裸数据
int count_i= -1;
Byte *contentByte = (Byte *)[h264file bytes];
Byte byte;
//h264是一个个NALU单元组成的,每个单元以00 00 01 或者 00 00 00 01分隔开来,每2个00 00 00 01之间就是一个NALU单元
for(int i=0;i<[h264file length];i++){
if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00&&contentByte[i+3]==0x01){ //分割符
[self.h264NALUArray addObject:[[NSMutableData alloc] init]];
i=i+3;
count_i++;
}else if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00){//分割符
[self.h264NALUArray addObject:[[NSMutableData alloc] init]];
i=i+2;
count_i++;
}else{
if(count_i>-1){
byte =contentByte[i];
[[self.h264NALUArray objectAtIndex:count_i] appendBytes:&byte length:sizeof(byte)];
}
}
}
}
2、封装FLV Header
// 1~9为FLV Header
// 前三位 0x46 0x4c 0x56为文件标识"FLV"
[flvData appendData:[@"FLV" dataUsingEncoding:NSUTF8StringEncoding]];
// 第四位是版本号
tempByte = 0x01;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//4
// 流的信息
tempByte = 0x01;//0x01--代表只有视频,0x04--只有音频,0x05--音频和视频混合
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//5
// 接下来的四位为Header的长度9Byte
writeIntegerToDataWithHex32(9, flvData);
3、封装SPS、PPS
//H.264码流第一个 NALU是 SPS(序列参数集Sequence Parameter Set)
//H.264码流第二个 NALU是 PPS(图像参数集Picture Parameter Set)
//Tag = Tag Header + Tag Data
//TAG Head 11
NSUInteger tagDataLength = topTagLen+ [[self.h264NALUArray objectAtIndex:0] length] + [[self.h264NALUArray objectAtIndex:1] length];
NSData *headerTagData = [self creatflvTagHeaderWithType:FLVTagHeaderTypeVideo tagDataSize:tagDataLength timeStamp:0];
[flvData appendData:headerTagData];
//
tempByte = 0x17;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0x00;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0x00;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0x00;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0x00;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
// AVC也就是H264的解码配置 configuretion
tempByte = 0x01;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0x42;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0x80;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0x0D;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0xFF;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
tempByte = 0xE1;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
// 写入前两个NALU单元,也就是SPS和PPS
NSData *NALU = [self.h264NALUArray objectAtIndex:0];
NSUInteger NALULength = [NALU length];
writeIntegerToDataWithHex16(NALULength, flvData);
[flvData appendData:NALU];
tempByte = 0x01;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//18
NALU = [self.h264NALUArray objectAtIndex:1];
NALULength = [NALU length];
writeIntegerToDataWithHex16(NALULength, flvData);
[flvData appendData:NALU];
4、按顺序封装NALU
NSInteger naluLength;
int time_h=0;//初始时间戳
for(int j=2;j<[self.h264NALUArray count];j++){
if(j==2){
naluLength =metaFixLen+[[self.h264NALUArray objectAtIndex:0] length]+[[self.h264NALUArray objectAtIndex:1] length];
}else{
naluLength = videoTagFixLen+[[self.h264NALUArray objectAtIndex:j-1] length];
}
// Previous Tag Size
writeIntegerToDataWithHex32(naluLength, flvData);
// 本次tag的Header Data长度为5+4长度
naluLength = [[self.h264NALUArray objectAtIndex:j] length]+9;
NSData *tagHeader = [self creatflvTagHeaderWithType:FLVTagHeaderTypeVideo tagDataSize:naluLength timeStamp:time_h];
[flvData appendData:tagHeader];
// Tag Data
// 一个byte的video信息+一个byte的AVCPacket type+3个bytes的无用数据(composition time,当AVC时无用,全是0)+ 4个bytes的NALU单元长度 + N个bytes的NALU数据
NALU = [self.h264NALUArray objectAtIndex:j];
Byte *contentByte = (Byte *)[NALU bytes];
if((contentByte[0]& 0x1f) == 5){
// 高4bits:1,keyframe。 低4bits:7,代表AVC
tempByte = 0x17;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//1
}else{
// 高4bits:2,inter frame ,P frame。 低4bits:7,AVC NALU
tempByte = 0x27;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//1
}
tempByte = 0x01;
[flvData appendBytes:&tempByte length:sizeof(tempByte)];//2
tempByte = 0x000000;
[flvData appendBytes:&tempByte length:3];//3、4、5
// NALU 长度写入
NSLog(@"len:%ld",(long)[NALU length]);
writeIntegerToDataWithHex32([NALU length], flvData); //6、7、8、9
// NALU数据写入
[flvData appendData:[self.h264NALUArray objectAtIndex:j]];
time_h=time_h+40;//对于一个裸h264流,没有时间戳的概念,可以默认以25fps,即40ms一帧数据
}//for
5、保存FLV文件
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *flvPath = [documentPath stringByAppendingPathComponent:@"a.flv"];
[flvData writeToFile:flvPath atomically:YES];