天天看點

海康威視實時預覽回調PS流用EasyRTMP向RTMP伺服器推流中視訊資料處理的代碼

在上一篇方案《EasyRTMP結合海康HCNetSDK擷取海康錄影機H.264實時流并轉化成為RTMP直播推流(附源碼)》我們介紹了将海康安防錄影機進行網際網路直播的整體方案流程,其中有一個流程“資料處理與分析”我們當時沒有展開詳述,今天我們将海康HCNetSDK實時預覽回調接口資料處理的過程,尤其是在RealDataCallback中對AVData的處理過程:

case NET_DVR_STREAMDATA:
			{
				BOOL inData=PlayM4_InputData(nPort,pBuffer,dwBufSize);
				while (!inData)
				{
					Sleep(10);
					inData=PlayM4_InputData(nPort,pBuffer,dwBufSize);
					OutputDebugString("PlayM4_InputData failed \n");	
				}	
				//PS流資料解析處理		
				{
					int nI = 0;
					int nCacheSize = 0;
					nI = m_mFrameCacheLenth[lRealHandle];

					//直接--提取H264資料
					BOOL bVideo = FALSE;
					BOOL bPatialData = FALSE;
					bPatialData = GetH246FromPS(pBuffer,dwBufSize, &m_pFrameCache[lRealHandle][nI].pCacheBuffer, 
						m_pFrameCache[lRealHandle][nI].nCacheBufLenth, bVideo);

					if (bVideo)
					{
						if (bPatialData)//部分包資料
						{
							//緩存資料
							m_pFrameCache[lRealHandle][nI].lTimeStamp = clock();
							m_mFrameCacheLenth[lRealHandle]++;
						} 
						else//標頭
						{
							int i = 0;
							if(m_mFrameCacheLenth[lRealHandle]>0)
							{
								long lH264DataLenth = m_mFrameCacheLenth[lRealHandle]*MAX_PACK_SIZE;
								BYTE* pH264Nal =  NULL;
								pH264Nal = new BYTE[lH264DataLenth];
								memset(pH264Nal, 0x00, lH264DataLenth);
								BYTE* pTempBuffer = pH264Nal;
								int nTempBufLenth = 0;

								//TRACE("m_mFrameCacheLenth==%d\r\n", pDemoDlg->m_mFrameCacheLenth);

								// 最大緩存資料個數設為pDemoDlg->m_mFrameCacheLenth,程式會過程中報錯,Why? [5/6/2014-13:19:51 Dingshuai]
								for (i=0; i</*MAX_FRAME_LENTH*/m_mFrameCacheLenth[lRealHandle]; i++)
								{
									if(m_pFrameCache[lRealHandle][i].pCacheBuffer!=NULL&&m_pFrameCache[lRealHandle][i].nCacheBufLenth>0)
									{
										// 										memcpy(pTempBuffer, m_pFrameCache[i].pCacheBuffer, m_pFrameCache[i].nCacheBufLenth);
										// 										pTempBuffer = pTempBuffer + m_pFrameCache[i].nCacheBufLenth;

										memcpy(pH264Nal+nTempBufLenth, m_pFrameCache[lRealHandle][i].pCacheBuffer, 
											m_pFrameCache[lRealHandle][i].nCacheBufLenth);
										nTempBufLenth += m_pFrameCache[lRealHandle][i].nCacheBufLenth;
									}
									if (m_pFrameCache[lRealHandle][i].pCacheBuffer)
									{
										delete [](m_pFrameCache[lRealHandle][i].pCacheBuffer);
										m_pFrameCache[lRealHandle][i].pCacheBuffer = NULL;
									}	
									m_pFrameCache[lRealHandle][i].nCacheBufLenth = 0; 
								}

								if (m_bRtmpRunning && pH264Nal && nTempBufLenth>0)
								{
									BOOL bIsKeyFrame = FALSE;
									//查找是否為關鍵幀
									if(pH264Nal[4]==0x67)
									{
										bIsKeyFrame = TRUE;
									}
									long lTimeStamp = clock();
									WriteH264DataToChace(lRealHandle, pH264Nal, nTempBufLenth, bIsKeyFrame, lTimeStamp);
								}

								if (pH264Nal)
								{
									delete []pH264Nal;
									pH264Nal = NULL;
								}
								// 緩存資料個數 清0
								m_mFrameCacheLenth[lRealHandle] = 0;
							}
						}
					}
				}
			}
           

通過對上述代碼的分析,整個視訊流回調過程還是比較簡單的:判斷回調資料類型(NET_DVR_STREAMDATA)–》海康PS資料Demux成音視訊ES資料(GetH246FromPS)–》對關鍵幀資料做緩存和處理(bIsKeyFrame )–》進入轉推RTMP緩存隊列(WriteH264DataToChace)

海康PS流解析:

BOOL CDecCallBack_DemoDlg::GetH246FromPS(IN BYTE* pBuffer, IN int nBufLenth, BYTE** pH264, int& nH264Lenth, BOOL& bVideo)
{
	if (!pBuffer || nBufLenth<=0)
	{
		return FALSE;
	}

	BYTE* pH264Buffer = NULL;
	int nHerderLen = 0;
	
	if( pBuffer
		&& pBuffer[0]==0x00
		&& pBuffer[1]==0x00 
		&& pBuffer[2]==0x01
		&& pBuffer[3]==0xE0)//E==視訊資料(此處E0辨別為視訊)
	{
		bVideo = TRUE;
		nHerderLen = 9 + (int)pBuffer[8];//9個為固定的資料標頭長度,pBuffer[8]為填充頭部分的長度
		pH264Buffer = pBuffer+nHerderLen;
		if (*pH264 == NULL)
		{
			*pH264 = new BYTE[nBufLenth];
		}
		if (*pH264&&pH264Buffer&&(nBufLenth-nHerderLen)>0)
		{	
			memcpy(*pH264, pH264Buffer, (nBufLenth-nHerderLen));
		}	
		nH264Lenth = nBufLenth-nHerderLen;
	
		return TRUE;
	}	
	else if(pBuffer 
		&& pBuffer[0]==0x00
		&& pBuffer[1]==0x00
		&& pBuffer[2]==0x01
		&& pBuffer[3]==0xC0) //C==音頻資料
	{
		*pH264 = NULL;
		nH264Lenth = 0;
		bVideo = FALSE;
	}
	else if(pBuffer 
		&& pBuffer[0]==0x00
		&& pBuffer[1]==0x00
		&& pBuffer[2]==0x01
		&& pBuffer[3]==0xBA)//視訊流資料包 標頭
	{
		bVideo = TRUE;
		*pH264 = NULL;
		nH264Lenth = 0;
		return FALSE;
	}
	return FALSE;
}
           

海康音視訊資料進行RTMP推流:

int CDecCallBack_DemoDlg::WriteH264DataToChace(int nDevId, BYTE* pBuffer, int nBufSize, BOOL bIsKeyFrame, long lTimeStamp)
{
	if (!pBuffer || nBufSize<=0 || lTimeStamp<0)
	{
		return -1;
	}

	BOOL bKeyFrame  = bIsKeyFrame;
	int nDeviceType = nDevId+1;
	if (m_RtmpHandle && m_bRtmpRunning)
	{
		//H264推送RTMP
		EASY_AV_Frame	avFrame;
		memset(&avFrame, 0x00, sizeof(EASY_AV_Frame));

		avFrame.pBuffer = (unsigned char*)pBuffer;
		avFrame.u32AVFrameLen = nBufSize;
		avFrame.u32VFrameType = (bKeyFrame)?EASY_SDK_VIDEO_FRAME_I:EASY_SDK_VIDEO_FRAME_P;
		avFrame.u32AVFrameFlag = EASY_SDK_VIDEO_FRAME_FLAG;
		avFrame.u32TimestampSec = lTimeStamp/1000000;
		avFrame.u32TimestampUsec = (lTimeStamp%1000000);

		//EnterCriticalSection(&m_cs);
		EasyRTMP_SendPacket(m_RtmpHandle, &avFrame);	
	}

	return 1;
}
           

經過上述步驟,基本完成了對一路SDK資料回調的處理,如果需要一個完整的、系統的、中間件級别的流轉服務,那還需要一套完整的控制機制和配套接口以及前端界面,就類似于EasyNVR一樣。

本文中所述詳細代碼見:https://github.com/EasyDSS/EasyRTMP/tree/master/EasyRTMP_HIK