公司最近想承接一个通过智能手机实现视频双向通讯的功能。提前开始了技术预研究。为保证较小的延迟,和优质的视频功能,我们对手机采集的音频和视频都利用手机硬件提供的硬编码功能直接实现H264+AAC编码。封包采用目前视频网站普遍使用的FLV格式。然后通过开源的RtmpLib库,以RTMP协议发送给音视频分发服务器。从而实现延迟很小的高质量视频通讯。
作为这个实施方案的第一步,我们需要分别实现Android和IOS手机上的H264裸流封装为FLV格式。我主要负责IOS部分的实现,经过2天的努力,终于实现了目标。看到VLC播放成功我封装的视频,实在非常有成就感。下面我把这几天的努力,总结下。
FLV是一个二进制文件,简单来说,其是由一个文件头(FLV header)和很多tag组成(FLV body)。tag又可以分成三类:audio,video,script,分别代表音频流,视频流,脚本流,而每个tag又由tag header和tag data组成。用通俗语言介绍下我的理解。FLV是音视频的容器。有固定长度的FLV Head和FLV Body组成。FLV Body由里面不同的TAG块组成。主要有3种,META TAG,在最前面,告诉后面视频或音频的头信息,从而告诉播放器播放码率和视频尺寸。这些信息都在H264裸流或AAC裸流的头部,可以直接提取封装,不需要我们解码。后面就是Video TAG视频TAG块和Audio TAG音频TAG块组成。本文只涉及视频部分,音频部分类似,不在本文述说了。
知道这些基本知识,要封装还是不容易的。我们现在通过截图来具体讲解。下面是H264裸流文件构成。
h264是一个个NALU单元组成的,每个单元以00 00 01 或者 00 00 00 01分隔开来,每2个00 00 00 01之间就是一个NALU单元。我们实际上就是将一个个NALU单元封装进FLV文件。头两个NAL块封装的数据描述了该视频的码率和版本都信息。后面就是按照时间顺序的视频数据块了。后面我们需要分割后,循环拼装到FLV格式中的。在IOS中可以用二维数组来分割和保存这些NAL块。代码如下:
-(void)initH264File:(NSString *)path{
NSInteger fuck = 0;
NSData * reader = [NSData dataWithContentsOfFile:path];//H264裸数据
[reader getBytes:&fuck length:sizeof(fuck)];
Byte *contentByte = (Byte *)[reader bytes];
NSLog(@"fuck:%d byte len:%d",fuck,[reader length]);
int count_i=-1;
Byte kk;
for(int i=0;i<[reader length];i++){
if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00&&contentByte[i+3]==0x01){
i=i+3;
count_i++;
[self.VideoListArray addObject:[[NSMutableData alloc] init]];
}
else if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00){
i=i+2;
count_i++;
[self.VideoListArray addObject:[[NSMutableData alloc] init]];
}
else{
if(count_i>-1){
kk=contentByte[i];
[[self.VideoListArray objectAtIndex:count_i] appendBytes:&kk length:sizeof(kk)];
}
}
}
}
后面我们看下FLV格式,首先看下图:
绿色标注的前9个Byte是FLVHead头信息。前三个是固定的'F','L','V'。前3个bytes是文件类型,总是“FLV”,也就是(0x46 0x4C 0x56)。第4btye是版本号,目前一般是0x01。第5byte是流的信息,倒数第一bit是1表示有视频(0x01),倒数第三bit是1表示有音频(0x4),有视频又有音频就是0x01 | 0x04(0x05),其他都应该是0。最后4bytes表示FLV 头的长度,3+1+1+4 = 9。 IOS代码可以如下实现:
NSMutableData *writer = [[NSMutableData alloc] init];
Byte i1 = 0x46;
[writer appendBytes:&i1 length:sizeof(i1)];//1
i1 = 0x4C;
[writer appendBytes:&i1 length:sizeof(i1)];//2
i1 = 0x56;
[writer appendBytes:&i1 length:sizeof(i1)];//3
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//4
i1 = 0x01;//0x01--代表只有视频,0x05--音频和视频混合,0x04--只有音频
[writer appendBytes:&i1 length:sizeof(i1)];//5
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//6
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//7
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//8
i1 = 0x09;
[writer appendBytes:&i1 length:sizeof(i1)];//9
然后讲解下META TAG,如下图: