SkeyePlayer(Windows)中录像采纳GPAC的MP4Box库来封装MP4,上面我将简略介绍MP4的封装调用流程和须要留神的点;
一、GPAC库的编译,GPAC是跨平台的库,windows和linux都能很不便多编译,再次不做过多赘述,大家可去GPAC官网或者Github上下载;
二、创立MP4
bool SkeyeMP4Writer::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;值得注意的中央是:
1>. 创立文件之前须要对所有的参数进行初始化,以及如果文件正在写入则须要将其敞开,这个操作次要是32位程序写的MP4文件大于4G可能呈现不能播放的问题,为了不便写MP4文件进行分片,这个将在系列文章后续中进行解说;2>. 大家能够看到上段代码有屏蔽了局部代码flag&ZOUTFILE_FLAG_VIDEO和flag&ZOUTFILE_FLAG_AUDIO的判断,这两段代码是用来在MP4文件中创立音频轨和视频轨(默认各只创立一个),请留神:如果这里曾经创立了音频和视频轨,然而后续的写入过程中如果只写音频或者视频的话,某些播放器可能是播不进去的(比方windows自带的播放器),所以,如果只写音频的话只须要创立音频轨就能够了,视频同理。
三、写入视频H264的SPS和PPS头信息
bool SkeyeMP4Writer::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头信息
//写入AAC信息bool SkeyeMP4Writer::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> 函数出入的头两个参数大家看起来有点费解,这里示意的是音频解码参数组合的一个串,具体格局解析如下:(这个原本想独自开一篇博客来专门论述的,然而鉴于没多少内容就在这里一并表述进去) 看上面代码段:
// 前五位为 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采样率 双声道音频,是不是很好了解呢!!!
五、解析H264帧写入MP4
限于篇幅,这里就不贴代码了(否则有靠代码凑字数的嫌疑,尽管我曾经贴了好多了 ,哈哈哈......),上面用文字描述,分三步走: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 SkeyeMP4Writer::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;}
六、AAC写入MP4(是否带ADTS头)
同写视频相似,写音频同样要先写如音频解码参数,上文曾经剖析过如何写解码参数,这里只需把解码参数信息组织成串,通过WriteAACInfo()函数写入即可。写音频数据,实现和视频一样,调用gf_isom_add_sample函数即可;须要留神:因为咱们曾经写入了音频解码信息,那么如果AAC数据中带有ADTS头,则须要去掉则7个字节的头,否则可能局部播放器不能失常播放,ADTS头以 0xFFF 开始;
七、写入MP4封装头,保留文件
保留文件,开释缓存和系统资源:
//保留文件bool SkeyeMP4Writer::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;}
获取更多信息
邮件:support@Skeyedarwin.org
WEB:www.SkeyeDarwin.org
Copyright © SkeyeDarwin.org 2012-2016
[外链图片转存中...(img-M61RFUJj-1652408925267)]