SkeyePlayer源码解析系列之录像写MP4
源码 解析 系列 录像 MP4 SkeyePlayer
2023-06-13 09:18:45 时间
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
相关文章
- 不看绝对后悔的@Async深度解析【不仅仅是源码那么简单】
- Postgresql源码(68)virtualxid锁的原理和应用场景
- 【说站】云贝连锁V2独立版V2.1.5源码-优化扫码登陆流程
- AudioRecord源码解读(3)
- 技术分享 | AlertManager 源码解析
- Postgresql源码(96)操作符的语法解析细节
- react源码解析12.状态更新流程
- react源码解析7.Fiber架构_2023-02-07
- 【Android 逆向】ART 函数抽取加壳 ① ( ART 下的函数抽取恢复时机 | 禁用 dex2oat 机制源码分析 )
- 用 IDEA 看源码的正确姿势!你掌握了吗?
- Spark-Sql源码解析之四 Optimizer: analyzed logical plan –> optimized logical plan详解大数据
- kafka源码解析之十六生产者流程(客户端如何向topic发送数据)详解编程语言
- Linux编译源码:从入门到精通(linux编译源文件)
- HashMap源码解析详解编程语言
- Java项目实战之简易博客系统开发(带源码和解析)
- 碰撞MySQL与JSP,强大源码打造动力(jspmysql源码)
- Linux设备驱动源码解析(linux设备驱动源码)
- openGauss数据库源码解析系列文章—— SQL引擎源解析(二)