H.264封装成FLV

H.264封装成FLV

Posted by Ted on May 8, 2018

一、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结构。

img

三、封装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];