Part 1flvtag組成
FLV 文件結構由 FLVheader和FLVBody組成。(注意flv文件是大端格式的)
FLV頭組成(以c為例子,一字節對齊):
FLVBody是由若干個Tag組成的;
Tag=Tag頭(11字節)+數據
- typedef struct _FLV_HEADER
- {
- char FLV[3];//={0x46,0x4c,0x56};
- char Ver; //版本號
- char StreamInfo;// 有視頻又有音頻就是0x01 | 0x04(0x05)
- int HeaderLen; /*****頭的長度*****/
- } 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:
- #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。
Part3 h264 封裝flv
我們現在開始把H264,AAC封裝為FLV文件。
首先定義一個函數(功能反向拷貝):
- void ReverseMemcpy(void* dest,size_t destLen, const void* src, size_t n)
- {
- char* d= (char*) dest;
- const char* s= (const char*)src;
- s=s+n-1;
- while(n--&&destLen--)
- {
- *d++=*s--;
- }
- return dest;
- }
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結構信息
- typedef struct _AVC_DEC_CON_REC
- {
- char cfgVersion;//configurationVersion //0x01
- char avcProfile;//AVCProfileIndication //sps[1]
- char profileCompatibility;//profile_Compatibility //sps[2]
- char avcLevel;//AVCLevelIndication //sps[3]
- //lengthSizeMinusOne:indicates the length in bytes of the NALUnitLength field in an AVC video
- char reserved6_lengthSizeMinusOne2;//
- char reserved3_numOfSPS5;//個數
- long spsLength;//sequenceParameterSetLength
- void *sps;
- char numOfPPS;//個數
- long ppsLength;
- void *pps;
- }AVC_DEC_CON_REC;
- char *pH264Data=....;//h264數據。
- int H264DataLen=....;//h264數據長度
- FLV_TAG_HEADER tagHeader;
- char AVCPacket[4]={0x00,0x00,0x00,0x00}
- memset(tagHeader,0,sizeof(FLV_TAG_HEADER));
- int Index=0;//分隔符長度
- if(*pH264Data==0x00&&(*pH264Data+1)==0x00&&(*pH264Data+2)==0x01)
- {
- Index=3;
- }else if(*pH264Data==0x00&&(*pH264Data+1)==0x00&&(*pH264Data+2)==0x00&&(*pH264Data+4)==0x01)
- {
- Index=4;
- }else{
- Err//錯誤不是h264數據
- }
- if(*(pH264Data+Index)&0x1f==0x07)//sps幀,此h264數據還有一幀,pps。
- {
- int PreTagLen=.....//前一個Tag長度
- ReverseMemcpy(&tagHeader.PreTagLen,4,&PreTagLen,4);//大端字節序;
- tagHeader.TagType=0x09;//視頻類型
- //AVCPacket應全為0x00.
- }
part 4.音頻AAC封裝flv。
AAC音頻格式有ADIF和ADTS:ADIF:Audio Data Interchange Format 音頻數據交換格式。這種格式的特征是可以確定的找到這個音頻數據的開始,不需進行在音頻數據流中間開始的解碼,即它的解碼必須在明確定義的開始處進行。故這種格式常用在磁盤文件中。ADTS:Audio Data Transport Stream 音頻數據傳輸流。這種格式的特征是它是一個有同步字的比特流,解碼可以在這個流中任何位置開始。它的特征類似於mp3數據流格式。簡單說,ADTS可以在任意幀解碼,也就是說它每一幀都有頭信息。ADIF只有一個統一的頭,所以必須得到所有的數據后解碼。且這兩種的header的格式也是不同的,目前一般編碼后的和抽取出的都是ADTS格式的音頻流。語音系統對實時性要求較高,基本是這樣一個流程,采集音頻數據,本地編碼,數據上傳,服務器處理,數據下發,本地解碼ADTS是幀序列,本身具備流特征,在音頻流的傳輸與處理方面更加合適。
因此我們在音頻編碼時常選擇ADTS的,以下是我們常用的配置
- m_hAacEncoder= faacEncOpen(capability.nSamplesPerSec,capability.nChannels,
- &m_nAacInputSamples, &m_nAacMaxOutputBytes);
- m_nAacnMaxInputBytes=m_nAacInputSamples*capability.wBitsPerSample/8;
- m_pAacConfig = faacEncGetCurrentConfiguration(m_hAacEncoder);//獲取配置結構指針
- m_pAacConfig->inputFormat = FAAC_INPUT_16BIT;//16精度
- m_pAacConfig->outputFormat=1; // 設置為 ADTS
- m_pAacConfig->useTns=true;
- m_pAacConfig->useLfe=false;
- m_pAacConfig->aacObjectType=LOW;
- m_pAacConfig->shortctl=SHORTCTL_NORMAL;
- m_pAacConfig->quantqual=100;
- m_pAacConfig->bandWidth=0;
- m_pAacConfig->bitRate=capability.nAvgBytesPerSec;
對於flv的aac音頻和視頻一樣需要在第一幀寫入配置信息。
- flv_packet flvpacket=GetErrPacket();
- int TagDataLen=1000;
- char *pTagBuffer=(char *)::malloc(TagDataLen);
- memset(pTagBuffer,0,TagDataLen);
- KKMEDIA::FLV_TAG_HEADER Tag_Head;
- memset(&Tag_Head,0,sizeof(Tag_Head));
- FlvMemcpy(&Tag_Head.PreTagLen,4,&m_nPreTagLen,4);
- memset(&Tag_Head.Timestamp,0,3);
- Tag_Head.TagType=0x08;///音頻
- int datalen=0;
- memcpy(pTagBuffer,&Tag_Head,sizeof(KKMEDIA::FLV_TAG_HEADER));
- datalen+=sizeof(KKMEDIA::FLV_TAG_HEADER);
- //前4bits表示音頻格式(全部格式請看官方文檔):
- //1 -- ADPCM
- //2 -- MP3
- //4 -- Nellymoser 16-kHz mono
- //5 -- Nellymoser 8-kHz mono
- //10 -- AAC
- //面兩個bits表示samplerate:
- //·0 -- 5.5KHz
- //·1 -- 11kHz
- //·2 -- 22kHz
- //·3 -- 44kHz 1100=0x0C
- //下面1bit表示采樣長度:
- //·0 -- snd8Bit
- //·1 -- snd16Bit
- //下面1bit表示類型:
- //·0 -- sndMomo
- //·1 -- sndStereo
- char TagAudio=0xAF; //1010,11,1,1
- //TagAudio &=0x0C;//3
- //TagAudio &=0x02;//1
- //TagAudio &=0x01;//sndStereo
- memcpy(pTagBuffer+datalen,&TagAudio,1);
- datalen++;
- char AACPacketType=0x00;//012->
- memcpy(pTagBuffer+datalen,&AACPacketType,1);
- datalen++;
- ///兩個字節
- char AudioSpecificConfig[2]={0x12,0x90};///32000hz
- memcpy(pTagBuffer+datalen,&AudioSpecificConfig,2);
- datalen+=2;
- m_nPreTagLen=datalen-4;///(tag長度值)
- TagDataLen=datalen-15;//(11+4(tag長度值+tag的頭)
- //Tag 數據區長度
- FlvMemcpy(pTagBuffer+5,3,&TagDataLen,3);
- flvpacket.buf =(unsigned char*)pTagBuffer;
- flvpacket.bufLen=datalen;
- flvpacket.taglen=m_nPreTagLen;
- return flvpacket;
關於上面的代碼中AudioSpecificConfig的值是怎樣計算來的,可以直接中aac編碼庫中獲取,或者采用公式計算出來,請看一下代碼。
- ///索引表
- static unsigned const samplingFrequencyTable[16] = {
- 96000, 88200, 64000, 48000,
- 44100, 32000, 24000, 22050,
- 16000, 12000, 11025, 8000,
- 7350, 0, 0, 0
- };
- int profile=1;
- int samplingFrequencyIndex=0;
- for(int i=0;i<16;i++)
- {
- if(samplingFrequencyTable[i]==32000)
- {
- samplingFrequencyIndex =i;
- break;
- }
- }
- char channelConfiguration =0x02;//(立體聲)
- UINT8 audioConfig[2] = {0};
- UINT8 const audioObjectType = profile + 1; ///其中profile=1;
- audioConfig[0] = (audioObjectType<<3) | (samplingFrequencyIndex>>1);
- audioConfig[1] = (samplingFrequencyIndex<<7) | (channelConfiguration<<3);
- printf("%02x%02x", audioConfig[0], audioConfig[1]);
最后就寫入aac幀數據了,請看以下代碼:
- flv_packet flvpacket=GetErrPacket();
- int TagDataLen=1000+srcLen;
- char *pTagBuffer=(char *)::malloc(TagDataLen);
- memset(pTagBuffer,0,TagDataLen);
- KKMEDIA::FLV_TAG_HEADER Tag_Head;
- memset(&Tag_Head,0,sizeof(Tag_Head));
- //FlvMemcpy等同於ReverseMemcpy
- FlvMemcpy(&Tag_Head.PreTagLen,4,&m_nPreTagLen,4);
- FlvMemcpy(&Tag_Head.Timestamp,3,&pts,3);
- Tag_Head.TagType=0x08;///音頻
- int datalen=0;
- memcpy(pTagBuffer,&Tag_Head,sizeof(KKMEDIA::FLV_TAG_HEADER));
- datalen+=sizeof(KKMEDIA::FLV_TAG_HEADER);
- char TagAudio=0xAF;
- memcpy(pTagBuffer+datalen,&TagAudio,1);
- datalen++;
- char AACPacketType=0x01;
- memcpy(pTagBuffer+datalen,&AACPacketType,1);
- datalen++;
- //src aac數據指針(不包含ADTS頭長度),srcLenAAC數據長度
- memcpy(pTagBuffer+datalen,src,srcLen);
- datalen+=srcLen;
- m_nPreTagLen=datalen-4;///(tag長度值)
- TagDataLen=datalen-15;//(11+4(tag長度值+tag的頭)
- //Tag 數據區長度
- FlvMemcpy(pTagBuffer+5,3,&TagDataLen,3);
- flvpacket.buf =(unsigned char*)pTagBuffer;
- flvpacket.bufLen=datalen;
- flvpacket.taglen=m_nPreTagLen;
- return flvpacket;
注意在使用ADTS頭輸出的AAC編碼數據,在打包flv格式時,需要跳過其adts頭長度(7字節)。
例如:
- AudioPacket((const unsigned char *)(pDataNALU+7),PktSize-7,Pts);
