RTSP播放器高RTSP兼容推流網頁無插件流媒體播放器EasyPlayer-RTSP關於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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM