zoukankan      html  css  js  c++  java
  • h264 aac 封装 flv

    Part 1flvtag组成

    FLV 文件结构由 FLVheader和FLVBody组成。(注意flv文件是大端格式的)
    FLV头组成(以c为例子,一字节对齐):
    FLVBody是由若干个Tag组成的;
         Tag=Tag头(11字节)+数据

    [cpp] view plain copy
     
    1. typedef struct _FLV_HEADER  
    2. {  
    3.     char FLV[3];//={0x46,0x4c,0x56};  
    4.     char Ver;   //版本号  
    5.     char StreamInfo;// 有视频又有音频就是0x01 | 0x04(0x05)  
    6.     int HeaderLen; /*****头的长度*****/  
    7. } FLV_HEADER;  

    Part2 h264

    H264是一个个NALU单元组成的,每个单元以00 00 01 或者 00 00 00 01分隔开来,每2个00 00 00 01之间就是一个NALU单元。我们实际上就是将一个个NALU单元封装进FLV文件。
    每个NALU单元开头第一个byte的低5bits表示着该单元的类型,即NAL nal_unit_type:

    [cpp] view plain copy
     
    1. #define NALU_TYPE_SLICE 1  
    2. #define NALU_TYPE_DPA 2  
    3. #define NALU_TYPE_DPB 3  
    4. #define NALU_TYPE_DPC 4  
    5. #define NALU_TYPE_IDR 5    /**关键帧***/  
    6. #define NALU_TYPE_SEI 6    /*****曾强帧******/       
    7. #define NALU_TYPE_SPS 7  
    8. #define NALU_TYPE_PPS 8  
    9. #define NALU_TYPE_AUD 9  
    10. #define NALU_TYPE_EOSEQ 10  
    11. #define NALU_TYPE_EOSTREAM 11  
    12. #define NALU_TYPE_FILL 12  



    每个NALU第一个byte & 0x1f 就可以得出它的类型,比如上图第一个NALU:67 & 0x1f = 7,则此单元是SPS,第三个:68 & 0x1f = 8,则此单元是PPS。

    Part3 h264 封装flv

     我们现在开始把H264,AAC封装为FLV文件。
     首先定义一个函数(功能反向拷贝):

    [cpp] view plain copy
     
    1. void ReverseMemcpy(void* dest,size_t destLen, const void* src, size_t n)  
    2. {  
    3.         char*     d= (char*) dest;  
    4.         const char*  s= (const char*)src;  
    5.         s=s+n-1;  
    6.         while(n--&&destLen--)  
    7.         {  
    8.             *d++=*s--;  
    9.         }  
    10.         return dest;  
    11. }  



                  1.写入FLV头。
                  2.写入FLV脚本Tag;
                  3.由于分装的是H264,AAC所以所写入一个视频配置信息,和一个音频配置信息
                  3.写入视频Tag.由于是H264,Tag的数据就需要按照AVC格式封装。Tag数据区有两种,一种是视频(0x17),一种是音频(0x27)。
                     AVC格式:AVCPacketType(1字节)+CompositionTime(3字节)
                                   如果AVCPacketType=0x00,这格式为AVCPacketType(1字节)+CompositionTime(3字节)+AVCDecoderConfigurationRecord。
                                   如果AVCPacketType=0x01,这格式为AVCPacketType(1字节)+CompositionTime(3字节)+ 4个bytes的NALU单元长度 + N个bytes的NALU数据。
    AVCDecoderConfigurationRecord结构信息    

    [cpp] view plain copy
     
    1. typedef struct _AVC_DEC_CON_REC  
    2. {  
    3.     char cfgVersion;//configurationVersion  //0x01  
    4.     char avcProfile;//AVCProfileIndication  //sps[1]  
    5.     char profileCompatibility;//profile_Compatibility //sps[2]  
    6.     char avcLevel;//AVCLevelIndication //sps[3]  
    7.     //lengthSizeMinusOne:indicates the length in bytes of the NALUnitLength field in an AVC video  
    8.     char reserved6_lengthSizeMinusOne2;//  
    9.     char reserved3_numOfSPS5;//个数  
    10.     long spsLength;//sequenceParameterSetLength  
    11.     void *sps;  
    12.     char numOfPPS;//个数  
    13.     long ppsLength;  
    14.     void *pps;  
    15. }AVC_DEC_CON_REC;  
    16.    
    17. char *pH264Data=....;//h264数据。  
    18. int H264DataLen=....;//h264数据长度  
    19. FLV_TAG_HEADER tagHeader;  
    20. char AVCPacket[4]={0x00,0x00,0x00,0x00}  
    21. memset(tagHeader,0,sizeof(FLV_TAG_HEADER));  
    22. int Index=0;//分隔符长度  
    23. if(*pH264Data==0x00&&(*pH264Data+1)==0x00&&(*pH264Data+2)==0x01)  
    24. {  
    25.      Index=3;  
    26. }else if(*pH264Data==0x00&&(*pH264Data+1)==0x00&&(*pH264Data+2)==0x00&&(*pH264Data+4)==0x01)  
    27. {  
    28.      Index=4;  
    29. }else{  
    30.     Err//错误不是h264数据  
    31. }  
    32. if(*(pH264Data+Index)&0x1f==0x07)//sps帧,此h264数据还有一帧,pps。  
    33. {  
    34.      int PreTagLen=.....//前一个Tag长度  
    35.      ReverseMemcpy(&tagHeader.PreTagLen,4,&PreTagLen,4);//大端字节序;  
    36.      tagHeader.TagType=0x09;//视频类型  
    37.      //AVCPacket应全为0x00.  
    38.        
    39. }  

    part 4.音频AAC封装flv。

        AAC音频格式有ADIF和ADTS:ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。简单说,ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,目前一般编码后的和抽取出的都是ADTS格式的音频流。语音系统对实时性要求较高,基本是这样一个流程,采集音频数据,本地编码,数据上传,服务器处理,数据下发,本地解码ADTS是帧序列,本身具备流特征,在音频流的传输与处理方面更加合适。
    因此我们在音频编码时常选择ADTS的,以下是我们常用的配置
      

    [cpp] view plain copy
     
    1.  m_hAacEncoder= faacEncOpen(capability.nSamplesPerSec,capability.nChannels,  
    2. &m_nAacInputSamples, &m_nAacMaxOutputBytes);  
    3.    m_nAacnMaxInputBytes=m_nAacInputSamples*capability.wBitsPerSample/8;  
    4.    m_pAacConfig = faacEncGetCurrentConfiguration(m_hAacEncoder);//获取配置结构指针  
    5.    m_pAacConfig->inputFormat = FAAC_INPUT_16BIT;//16精度  
    6. m_pAacConfig->outputFormat=1; //   设置为 ADTS     
    7. m_pAacConfig->useTns=true;  
    8. m_pAacConfig->useLfe=false;  
    9. m_pAacConfig->aacObjectType=LOW;  
    10. m_pAacConfig->shortctl=SHORTCTL_NORMAL;  
    11. m_pAacConfig->quantqual=100;  
    12. m_pAacConfig->bandWidth=0;  
    13. m_pAacConfig->bitRate=capability.nAvgBytesPerSec;  



    对于flv的aac音频和视频一样需要在第一帧写入配置信息。
     

    [cpp] view plain copy
     
    1. flv_packet flvpacket=GetErrPacket();          
    2.  int TagDataLen=1000;  
    3.  char *pTagBuffer=(char *)::malloc(TagDataLen);  
    4.  memset(pTagBuffer,0,TagDataLen);  
    5.  KKMEDIA::FLV_TAG_HEADER Tag_Head;  
    6.  memset(&Tag_Head,0,sizeof(Tag_Head));  
    7.  FlvMemcpy(&Tag_Head.PreTagLen,4,&m_nPreTagLen,4);  
    8.  memset(&Tag_Head.Timestamp,0,3);  
    9.  Tag_Head.TagType=0x08;///音频  
    10.  int datalen=0;  
    11.  memcpy(pTagBuffer,&Tag_Head,sizeof(KKMEDIA::FLV_TAG_HEADER));  
    12.  datalen+=sizeof(KKMEDIA::FLV_TAG_HEADER);  
    13.  //前4bits表示音频格式(全部格式请看官方文档):  
    14.  //1 -- ADPCM  
    15.  //2 -- MP3  
    16.  //4 -- Nellymoser 16-kHz mono  
    17.  //5 -- Nellymoser 8-kHz mono  
    18.  //10 -- AAC  
    19.  //面两个bits表示samplerate:  
    20.  //·0 -- 5.5KHz  
    21.  //·1 -- 11kHz  
    22.  //·2 -- 22kHz  
    23.  //·3 -- 44kHz 1100=0x0C  
    24.  //下面1bit表示采样长度:  
    25.  //·0 -- snd8Bit  
    26.  //·1 -- snd16Bit  
    27.  //下面1bit表示类型:  
    28.  //·0 -- sndMomo  
    29.  //·1 -- sndStereo    
    30.  char TagAudio=0xAF; //1010,11,1,1  
    31.  //TagAudio &=0x0C;//3  
    32.  //TagAudio &=0x02;//1  
    33.  //TagAudio &=0x01;//sndStereo  
    34.  memcpy(pTagBuffer+datalen,&TagAudio,1);  
    35.  datalen++;  
    36.  char AACPacketType=0x00;//012->  
    37.  memcpy(pTagBuffer+datalen,&AACPacketType,1);  
    38.  datalen++;  
    39.  ///两个字节  
    40.  char AudioSpecificConfig[2]={0x12,0x90};///32000hz  
    41.  memcpy(pTagBuffer+datalen,&AudioSpecificConfig,2);  
    42.  datalen+=2;  
    43.  m_nPreTagLen=datalen-4;///(tag长度值)  
    44.  TagDataLen=datalen-15;//(11+4(tag长度值+tag的头)  
    45.  //Tag 数据区长度  
    46.  FlvMemcpy(pTagBuffer+5,3,&TagDataLen,3);  
    47.  flvpacket.buf =(unsigned char*)pTagBuffer;            
    48.  flvpacket.bufLen=datalen;  
    49.  flvpacket.taglen=m_nPreTagLen;  
    50.  return flvpacket;  



    关于上面的代码中AudioSpecificConfig的值是怎样计算来的,可以直接中aac编码库中获取,或者采用公式计算出来,请看一下代码。

    [cpp] view plain copy
     
    1. ///索引表  
    2. static unsigned const samplingFrequencyTable[16] = {  
    3.   96000, 88200, 64000, 48000,  
    4.   44100, 32000, 24000, 22050,  
    5.   16000, 12000, 11025, 8000,  
    6.   7350,  0,     0,      0  
    7. };  
    8. int profile=1;  
    9. int samplingFrequencyIndex=0;  
    10. for(int i=0;i<16;i++)  
    11. {  
    12.         if(samplingFrequencyTable[i]==32000)  
    13.         {  
    14.            samplingFrequencyIndex =i;  
    15.            break;  
    16.         }  
    17. }  
    18. char channelConfiguration =0x02;//(立体声)  
    19. UINT8 audioConfig[2] = {0};    
    20. UINT8 const audioObjectType = profile + 1;  ///其中profile=1;  
    21. audioConfig[0] = (audioObjectType<<3) | (samplingFrequencyIndex>>1);    
    22. audioConfig[1] = (samplingFrequencyIndex<<7) | (channelConfiguration<<3);    
    23. printf("%02x%02x", audioConfig[0], audioConfig[1]);  



       最后就写入aac帧数据了,请看以下代码:
                   

    [cpp] view plain copy
     
    1. flv_packet flvpacket=GetErrPacket();  
    2.             int TagDataLen=1000+srcLen;  
    3.             char *pTagBuffer=(char *)::malloc(TagDataLen);  
    4.             memset(pTagBuffer,0,TagDataLen);  
    5.             KKMEDIA::FLV_TAG_HEADER Tag_Head;  
    6.             memset(&Tag_Head,0,sizeof(Tag_Head));  
    7.             //FlvMemcpy等同于ReverseMemcpy  
    8.             FlvMemcpy(&Tag_Head.PreTagLen,4,&m_nPreTagLen,4);  
    9.             FlvMemcpy(&Tag_Head.Timestamp,3,&pts,3);  
    10.   
    11.   
    12.             Tag_Head.TagType=0x08;///音频  
    13.             int datalen=0;  
    14.             memcpy(pTagBuffer,&Tag_Head,sizeof(KKMEDIA::FLV_TAG_HEADER));  
    15.             datalen+=sizeof(KKMEDIA::FLV_TAG_HEADER);  
    16.             char TagAudio=0xAF;   
    17.             memcpy(pTagBuffer+datalen,&TagAudio,1);  
    18.             datalen++;  
    19.   
    20.   
    21.             char AACPacketType=0x01;  
    22.             memcpy(pTagBuffer+datalen,&AACPacketType,1);  
    23.             datalen++;  
    24.               
    25.                //src aac数据指针(不包含ADTS头长度),srcLenAAC数据长度  
    26.             memcpy(pTagBuffer+datalen,src,srcLen);  
    27.             datalen+=srcLen;  
    28.   
    29.   
    30.             m_nPreTagLen=datalen-4;///(tag长度值)  
    31.             TagDataLen=datalen-15;//(11+4(tag长度值+tag的头)  
    32.   
    33.   
    34.             //Tag 数据区长度  
    35.             FlvMemcpy(pTagBuffer+5,3,&TagDataLen,3);  
    36.             flvpacket.buf =(unsigned char*)pTagBuffer;             
    37.             flvpacket.bufLen=datalen;  
    38.             flvpacket.taglen=m_nPreTagLen;  
    39.             return flvpacket;  



    注意在使用ADTS头输出的AAC编码数据,在打包flv格式时,需要跳过其adts头长度(7字节)。
    例如:

    [cpp] view plain copy
     
      1. AudioPacket((const unsigned  char *)(pDataNALU+7),PktSize-7,Pts);     
  • 相关阅读:
    算法:字符串处理
    写点什么好呢3?昨日的宝贝成了今日的负担!
    商业研究(22):股权投资,大有可为?
    商业研究(22):股权投资,大有可为?
    .Net Task常见问题
    使用OKHttp模拟登陆知乎,兼谈OKHttp中Cookie的使用!
    Android开发——Android 6.0权限管理机制详解
    创业有套路
    创业有套路
    半分钟内能看透问题本质的人是如何思考的?
  • 原文地址:https://www.cnblogs.com/lidabo/p/9020423.html
Copyright © 2011-2022 走看看