EasyPlayer-RTSP播放器是一套RTSP專用的播放器,包括有:Windows(支持IE插件,npapi插件)、Android、iOS三個平臺,是區別於市面上大部分的通用播放器,EasyPlayer-RTSP更加精煉、更加專一,具有低延時和高RTSP協議兼容性,編碼數據解析等方面,都有很是大的優點。linux
EasyPlayer-RTSP-Win中錄像採用GPAC的MP4Box庫來封裝MP4,下面我將簡單介紹MP4的封裝調用流程和須要注意的點;ios
bool EasyMP4Writer::CreateMP4File(char*filename,int flag) { SaveFile(); m_audiostartimestamp=-1; m_videostartimestamp=-1; if(filename==NULL) { char filename2[256]={0}; sprintf(filename2,"%d-gpac%d.mp4",time(NULL),rand()); p_file=gf_isom_open(filename2,GF_ISOM_OPEN_WRITE,NULL);//打開文件 }else p_file=gf_isom_open(filename,GF_ISOM_OPEN_WRITE,NULL);//打開文件 if (p_file==NULL) { return false; } gf_isom_set_brand_info(p_file,GF_ISOM_BRAND_MP42,0); //if(flag&ZOUTFILE_FLAG_VIDEO) //{ // m_videtrackid=gf_isom_new_track(p_file,0,GF_ISOM_MEDIA_VISUAL,1000); // gf_isom_set_track_enabled(p_file,m_videtrackid,1); //} //if(flag&ZOUTFILE_FLAG_AUDIO) //{ // m_audiotrackid=gf_isom_new_track(p_file,0,GF_ISOM_MEDIA_AUDIO,1000); // gf_isom_set_track_enabled(p_file,m_audiotrackid,1); //} m_nCreateFileFlag = flag; return true; }
建立MP4很簡單,調用gf_isom_open函數就能輕鬆搞定,gf_isom_set_brand_info函數設置當前寫MP4的版本爲MP4V2;值得注意的地方是:windows
1>. 建立文件以前須要對全部的參數進行初始化,以及若是文件正在寫入則須要將其關閉,這個操做主要是32位程序寫的MP4文件大於4G可能出現不能播放的問題,爲了方便寫MP4文件進行分片,這個將在系列文章後續中進行講解;
2>. 你們能夠看到上段代碼有屏蔽了部分代碼flag&ZOUTFILE_FLAG_VIDEO和flag&ZOUTFILE_FLAG_AUDIO的判斷,這兩段代碼是用來在MP4文件中建立音頻軌和視頻軌(默認各只建立一個),請注意:若是這裏已經建立了音頻和視頻軌,然然後續的寫入過程當中若是隻寫音頻或者視頻的話,某些播放器多是播不出來的(好比windows自帶的播放器),因此,若是隻寫音頻的話只須要建立音頻軌就能夠了,視頻同理。api
bool EasyMP4Writer::WriteH264SPSandPPS(unsigned char*sps,int spslen,unsigned char*pps,int ppslen,int width,int height) { if (m_nCreateFileFlag&ZOUTFILE_FLAG_VIDEO) { m_videtrackid = gf_isom_new_track(p_file, 0, GF_ISOM_MEDIA_VISUAL, 1000); gf_isom_set_track_enabled(p_file, m_videtrackid, 1); } else { return false; } p_videosample=gf_isom_sample_new(); p_videosample->data=(char*)malloc(1024*1024); p_config=gf_odf_avc_cfg_new(); gf_isom_avc_config_new(p_file,m_videtrackid,p_config,NULL,NULL,&i_videodescidx); gf_isom_set_visual_info(p_file,m_videtrackid,i_videodescidx,width,height); GF_AVCConfigSlot m_slotsps={0}; GF_AVCConfigSlot m_slotpps={0}; p_config->configurationVersion = 1; p_config->AVCProfileIndication = sps[1]; p_config->profile_compatibility = sps[2]; p_config->AVCLevelIndication = sps[3]; m_slotsps.size=spslen; m_slotsps.data=(char*)malloc(spslen); memcpy(m_slotsps.data,sps,spslen); gf_list_add(p_config->sequenceParameterSets,&m_slotsps); m_slotpps.size=ppslen; m_slotpps.data=(char*)malloc(ppslen); memcpy(m_slotpps.data,pps,ppslen); gf_list_add(p_config->pictureParameterSets,&m_slotpps); gf_isom_avc_config_update(p_file,m_videtrackid,1,p_config); free(m_slotsps.data); free(m_slotpps.data); return true; }
首先,經過gf_odf_avc_cfg_new()建立一個設置AVC信息的配置結構p_config,而後對結構中指定的信息,如:長,寬,SPS和PPS等關鍵參數寫入配置結構,調用gf_isom_avc_config_update函數寫入參數信息;固然這裏只是H264格式的參數設置,像其餘的格式好比H265的設置也相似,這將在後續系列中進行講解;數組
//寫入AAC信息緩存
bool EasyMP4Writer::WriteAACInfo(unsigned char*info,int len, int nSampleRate, int nChannel, int nBitsPerSample) { if (m_nCreateFileFlag&ZOUTFILE_FLAG_AUDIO) { m_audiotrackid = gf_isom_new_track(p_file, 0, GF_ISOM_MEDIA_AUDIO, 1000); gf_isom_set_track_enabled(p_file, m_audiotrackid, 1); } else { return false; } p_audiosample=gf_isom_sample_new(); p_audiosample->data=(char*)malloc(1024*10); GF_ESD*esd= gf_odf_desc_esd_new(0); esd->ESID=gf_isom_get_track_id(p_file,m_audiotrackid); esd->OCRESID=gf_isom_get_track_id(p_file,m_audiotrackid); esd->decoderConfig->streamType=0x05; esd->decoderConfig->objectTypeIndication=0x40;//0x40; esd->slConfig->timestampResolution=1000;//1000;//時間單元 esd->decoderConfig->decoderSpecificInfo=(GF_DefaultDescriptor*)gf_odf_desc_new(GF_ODF_DSI_TAG); esd->decoderConfig->decoderSpecificInfo->data=(char*)malloc(len); memcpy(esd->decoderConfig->decoderSpecificInfo->data,info,len); esd->decoderConfig->decoderSpecificInfo->dataLength=len; GF_Err gferr=gf_isom_new_mpeg4_description(p_file, m_audiotrackid, esd, NULL, NULL, &i_audiodescidx); if (gferr!=0) { // TRACE("mpeg4_description:%d\n",gferr); } gferr=gf_isom_set_audio_info(p_file,m_audiotrackid,i_audiodescidx, nSampleRate,nChannel, nBitsPerSample);//44100 2 16 if (gferr!=0) { // TRACE("gf_isom_set_audio:%d\n",gferr); } free(esd->decoderConfig->decoderSpecificInfo->data); return true; }
調幾個 API就搞定了,一如既往的簡單–!,這裏說一下一些關鍵參數的配置:
1> esd->decoderConfig->streamType=0x05,這裏的0x05標示爲AAC,固然還指出其餘的類型,如MP3,AC3等等,具體可查詢MP4BOX相關文檔獲取;
2> 函數出入的頭兩個參數你們看起來有點費解,這裏表示的是音頻解碼參數組合的一個串,具體格式解析以下:(這個原本想單獨開一篇博客來專門闡述的,可是鑑於沒多少內容就在這裏一併表述出來)
看下面代碼段:ide
// 前五位爲 AAC object types LOW 2 // 接着4位爲 碼率index 16000 8 // 採樣標誌標準: // static unsigned long tnsSupportedSamplingRates[13] = //音頻採樣率標準(標誌),下標爲寫入標誌 // { 96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,0 }; // 接着4位爲 channels 個數 2 // 最後3位用0補齊 // 應打印出的正確2進制形式爲 00010 | 1000 | 0010 | 000 // 2 8 2 // BYTE ubDecInfoBuff[] = {0x12,0x10};//00010 0100 0010 000 //音頻採樣率標準(標誌),下標爲寫入標誌 unsigned long tnsSupportedSamplingRates[13] = { 96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,0 }; int nI = 0; for ( nI = 0; nI<13; nI++) { if (tnsSupportedSamplingRates[nI] == sample_rate ) { break; } } unsigned char ucDecInfoBuff[2] = {0x12,0x10};// unsigned short nDecInfo = (1<<12) | (nI << 7) | (channels<<3); int nSize = sizeof(unsigned short); memcpy(ucDecInfoBuff, &nDecInfo, nSize); SWAP(ucDecInfoBuff[0], ucDecInfoBuff[1]); int unBuffSize = sizeof(ucDecInfoBuff)*sizeof(unsigned char);
你們看懂了吧,好比如今有個表示解碼信息的串爲 00010 | 0100 | 0010 | 000 ,那麼它則表示爲AAC-LC 44100採樣率 雙聲道音頻,是否是很好理解呢!!!函數
下面用文字描述,分三步走:
1> 解析H264 nal頭,獲取SPS和PPS, 由於咱們已經經過設置函數設置了SPS和PPS等解碼關鍵信息,因此咱們寫入文件時,H264幀將轉換爲AVC格式,什麼意思,就是說將以00000001以及000001開頭的NAL單元轉換爲以該NAL單元的長度來填滿該四個字節(注意:全部的H264幀中的0x00000001和0x000001都要替換成NAL的長度,不然未替換的部分解碼會花屏),默認三個字節的000001也用四個字節補齊,這主要是見於一幀多NAL的狀況,這裏有疑問我將在後續系列文章中講解;
2> 寫入SPS和PPS頭;
3> 寫入以NAL長度爲頭四個字節的AVC幀,具體實現以下:
//寫入一幀,前四字節爲該幀NAL長度編碼
bool EasyMP4Writer::WriteVideoFrame(unsigned char*data,int len,bool keyframe,long timestamp) { if (!p_videosample) { return false; } if (m_videostartimestamp==-1&&keyframe) { m_videostartimestamp=timestamp; } if (m_videostartimestamp!=-1) { p_videosample->IsRAP=keyframe; p_videosample->dataLength=len; memcpy(p_videosample->data,data,len); p_videosample->DTS=timestamp-m_videostartimestamp; p_videosample->CTS_Offset=0; GF_Err gferr=gf_isom_add_sample(p_file,m_videtrackid,i_videodescidx,p_videosample); if (gferr==-1) { p_videosample->DTS=timestamp-m_videostartimestamp+15; gf_isom_add_sample(p_file,m_videtrackid,i_videodescidx,p_videosample); } } return true; }
同寫視頻相似,寫音頻一樣要先寫如音頻解碼參數,上文已經分析過如何寫解碼參數,這裏只需把解碼參數信息組織成串,經過WriteAACInfo()函數寫入便可。
寫音頻數據,實現和視頻同樣,調用gf_isom_add_sample函數便可;
須要注意:由於咱們已經寫入了音頻解碼信息,那麼若是AAC數據中帶有ADTS頭,則須要去掉則7個字節的頭,不然可能部分播放器不能正常播放,ADTS頭以 0xFFF 開始;spa
保存文件,釋放緩存和系統資源:
//保存文件
bool EasyMP4Writer::SaveFile() { if (m_psps) { delete m_psps; m_psps = NULL; } if (m_ppps) { delete m_ppps; m_ppps = NULL; } m_spslen=0; m_ppslen=0; if (m_pvps) { delete m_pvps; m_pvps = NULL; } m_vpslen = 0; m_audiostartimestamp=-1; m_videostartimestamp=-1; if (p_file) { gf_isom_close(p_file); p_file=NULL; } if(p_config) { // delete p_config->pictureParameterSets; p_config->pictureParameterSets=NULL; // delete p_config->sequenceParameterSets; p_config->sequenceParameterSets=NULL; gf_odf_avc_cfg_del(p_config); p_config=NULL; } if (p_hevc_config) { gf_odf_hevc_cfg_del(p_hevc_config); p_hevc_config = NULL; } if( p_audiosample) { if( p_audiosample->data) { free(p_audiosample->data); p_audiosample->data=NULL; } gf_isom_sample_del(&p_audiosample); p_audiosample=NULL; } if( p_videosample) { if( p_videosample->data) { free(p_videosample->data); p_videosample->data=NULL; } gf_isom_sample_del(&p_videosample); p_audiosample=NULL; } m_bwriteaudioinfo = false; m_bwritevideoinfo = false; return true; }