zoukankan      html  css  js  c++  java
  • RTSP播放器高RTSP兼容推流网页无插件流媒体播放器EasyPlayerRTSP关于MP4的封装调用流程介绍

    EasyPlayer-RTSP播放器是一套RTSP专用的播放器,包括有:Windows(支持IE插件,npapi插件)、Android、iOS三个平台,是区别于市面上大部分的通用播放器,EasyPlayer-RTSP更加精炼、更加专注,具备低延时和高RTSP协议兼容性,编码数据解析等方面,都有非常大的优势。

    EasyPlayer-RTSP-Win中录像采用GPAC的MP4Box库来封装MP4,下面我将简单介绍MP4的封装调用流程和需要注意的点;

    一、GPAC库的编译,GPAC是跨平台的库,windows和linux都能很方便多编译,再次不做过多赘述,大家可去GPAC官网或者Github上下载;

    二、创建MP4

    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;值得注意的地方是:

    1>. 创建文件之前需要对所有的参数进行初始化,以及如果文件正在写入则需要将其关闭,这个操作主要是32位程序写的MP4文件大于4G可能出现不能播放的问题,为了方便写MP4文件进行分片,这个将在系列文章后续中进行讲解;
    2>. 大家可以看到上段代码有屏蔽了部分代码flag&ZOUTFILE_FLAG_VIDEO和flag&ZOUTFILE_FLAG_AUDIO的判断,这两段代码是用来在MP4文件中创建音频轨和视频轨(默认各只创建一个),请注意:如果这里已经创建了音频和视频轨,然而后续的写入过程中如果只写音频或者视频的话,某些播放器可能是播不出来的(比如windows自带的播放器),所以,如果只写音频的话只需要创建音频轨就可以了,视频同理。

    三、写入视频H264的SPS和PPS头信息

    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头信息

    //写入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> 函数出入的头两个参数大家看起来有点费解,这里表示的是音频解码参数组合的一个串,具体格式解析如下:(这个本来想单独开一篇博客来专门阐述的,但是鉴于没多少内容就在这里一并表述出来)
    看下面代码段:

    	    // 		前五位为 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 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;
    }
    

    六、AAC写入MP4(是否带ADTS头)

    同写视频类似,写音频同样要先写如音频解码参数,上文已经分析过如何写解码参数,这里只需把解码参数信息组织成串,通过WriteAACInfo()函数写入即可。
    写音频数据,实现和视频一样,调用gf_isom_add_sample函数即可;
    需要注意:因为我们已经写入了音频解码信息,那么如果AAC数据中带有ADTS头,则需要去掉则7个字节的头,否则可能部分播放器不能正常播放,ADTS头以 0xFFF 开始;

    七、写入MP4封装头,保存文件

    保存文件,释放缓存和系统资源:
    //保存文件

    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;
    }
  • 相关阅读:
    李清华 201772020113《面向对象程序设计(java)》第十四周学习总结
    李清华201772020113《面向对象程序设计(java)》第十三周学习总结
    201772020113李清华《面向对象程序设计(java)》第十二周学习总结
    李清华201772020113《面向对象程序设计(java)》第十一周学习总结
    2020软件工程作业00——问题清单
    2020软件工程个人作业06
    2020软件工程作业04
    2020软件工程作业03
    2020软件工程作业01
    2020软件工程个人作业06——软件工程实践总结作业
  • 原文地址:https://www.cnblogs.com/TSINGSEE/p/11721393.html
Copyright © 2011-2022 走看看