實現對rtp H264碼流的組幀
阿新 • • 發佈:2019-01-05
rtp打包h264,包含了三種類型的包:
- 一個rtp包攜帶了一幀資料(single)
- 多個rtp包攜帶了一幀資料(FU-A)
- 一個rtp包攜帶了多幀資料(STAP-A)
在實際應用中絕大部分採用的是前兩種方式,對方式1常見的是對nalu的sps,pps進行打包,因為sps和pps資料量很小,一個rtp包足以攜帶,一般採用 sps,pps分別由一個rtp包攜帶的方式。對IDR資料及其他型別資料通常是採用方式2,因為視訊幀資料通常比較大,一個rtp包不足以攜帶,分成多個rtp包攜帶,分包攜帶後對最後一個rtp包的mark欄位是要設定為true的。包格式定義詳細見 rfc3984
以下程式碼實現了對FU-A和single 格式的rtp包進行組幀,組好的一幀資料可以直接送至解碼器進行解碼。
#ifndef TYPEDEF_H #define TYPEDEF_H enum enNaluType { enNaluType_UnKnow = -1, //單幀即一個rtp包中就帶了一幀資料 enNaluType_Single = 0, //FUA的開頭 enNaluType_FUAStart = 1, //FUA資料幀 enNaluType_FUA = 2, //FUA的結束 enNaluType_FUAEnd = 3 }; struct SPacketParams { SPacketParams() { bMark = false; type = enNaluType_UnKnow; H264NalHeader = 0; } bool bMark; enNaluType type; //Nal單元的頭一個位元組用於判斷幀型別 unsigned char H264NalHeader }; #endif
#ifndef H264_FRAME_H #define H264_FRAME_H #include "typedef.h" //H264解包類,實現組幀操作 class CH264FrameUnpack { public: CH264FrameUnpack(); ~CH264FrameUnpack(); void ResetFramePool(); //傳入原始視訊資料 int SetFromData(unsigned char* pData,int iSize,SPacketParams ¶ms); int GetFrameSize() { return m_iEncodeFrameLen; } //獲取組成的H264一幀資料 unsigned char* GetFramePtr() { return m_pEncodeFrame; } private: void AddData(unsigned char* pData,int iDataLen,unsigned char* pNaluHeader,bool bAddHeader); private: unsigned char *m_pEncodeFrame; unsigned int m_iEncodeFrameLen; unsigned int m_iEncodeFrameMax; }; #endif
#include <stdlib.h>
#include <memory.h>
#include "H264FrameUnpack.h"
#define MAX_FRAME_SIZE 1024 * 1024
CH264FrameUnpack::CH264FrameUnpack():m_pEncodeFrame(NULL),m_iEncodeFrameLen(0)
{
m_iEncodeFrameLen = MAX_FRAME_SIZE;
m_iEncodeFrameMax = MAX_FRAME_SIZE;
}
CH264FrameUnpack::~CH264FrameUnpack()
{
m_iEncodeFrameLen = 0;
if (m_pEncodeFrame)
{
free(m_pEncodeFrame);
m_pEncodeFrame = NULL;
}
}
void CH264FrameUnpack::ResetFramePool()
{
m_iEncodeFrameLen = 0;
if (m_pEncodeFrame == NULL)
{
m_pEncodeFrame = (unsigned char*)malloc(MAX_FRAME_SIZE);
memset(m_pEncodeFrame,0,MAX_FRAME_SIZE);
}
}
void CH264FrameUnpack::AddData(unsigned char* pData,int iDataLen,unsigned char* pNaluHeader,bool bAddHeader)
{
int iHeaderLen = 0;
if (bAddHeader)
{
//NalU的起始四位元組加上NALU頭的一位元組
iHeaderLen = 5;
}
unsigned char *pCurrentPosiotionInFrame = m_pEncodeFrame + m_iEncodeFrameLen;
while (m_iEncodeFrameLen + iDataLen + iHeaderLen > m_iEncodeFrameMax)
{
m_iEncodeFrameMax += MAX_FRAME_SIZE;
m_pEncodeFrame = (unsigned char*)realloc(m_pEncodeFrame,m_iEncodeFrameMax);
pCurrentPosiotionInFrame = m_pEncodeFrame + m_iEncodeFrameLen;
}
if (bAddHeader)
{
*pCurrentPosiotionInFrame++ = 0;
*pCurrentPosiotionInFrame++ = 0;
*pCurrentPosiotionInFrame++ = 0;
*pCurrentPosiotionInFrame++ = 1;
//設定NALU的頭
memcpy(pCurrentPosiotionInFrame,pNaluHeader,1);
pCurrentPosiotionInFrame += 1;
}
memcpy(pCurrentPosiotionInFrame, pData, iDataLen);
m_iEncodeFrameLen += iDataLen+ iHeaderLen;
}
int CH264FrameUnpack::SetFromData(unsigned char* pData,int iSize,SPacketParams ¶ms)
{//根據包型別判斷是否加入頭
if (enNaluType_Single == params.type)
{
AddData(pData,iSize,params.H264NalHeader,true);
}
else if (enNaluType_FUAStart == params.type)
{
AddData(pData,iSize,params.H264NalHeader,true);
}
else if (enNaluType_FUA == params.type || enNaluType_FUAEnd == params.type)
{
AddData(pData,iSize,NULL,false);
}
return 0;
}
以下為呼叫示例程式碼,省略了取rtp包資料及獲取h264幀資料後的解碼操作:
#include <iostream>
#include "H264FrameUnpack.h"
//pData為去掉了rtp頭的rtp payload資料,isMark標示是否為一幀的最後一個rtp包
int ProcessH264Video(unsigned char* pData,unsigned int iSize,bool isMark,CH264FrameUnpack &h264unpack)
{
if (NULL == pData)
{
return -1;
}
unsigned char* pHeaderStart = pData;
unsigned int PacketSize = iSize;
int type = pHeaderStart[0] & 0x1F;
int iPacketSize = iSize;
if (type == 28)
{//FU-A
unsigned char startBit = pHeaderStart[1]>>7;
unsigned char endBit = (pHeaderStart[1]&0x40)>>6;
SPacketParams params;
params.bMark = isMark;
if(startBit)
{//FU-A的起始
pData++;
iPacketSize--;
params.type = enNaluType_FUAStart;
params.H264NalHeader = ((*(pPacketData->pData-1)) & 0xE0) | (pPacketData->pData[0] & 0x1F);
}
else
{// end
pData += 2;
iPacketSize -= 2;
if(endBit)
params.type = enNaluType_FUAEnd;
else
params.type = enNaluType_FUA;
}
h264unpack.SetFromData(pData,iPacketSize,params);
}
else if (24 == type)
{//STAP_A
return 0;
}
else
{
SPacketParams params;
params.type = enNaluType_Single;
params.H264NalHeader = pData[0];
h264unpack.SetFromData(pData,iPacketSize,params);
}
return 0;
}
int main()
{
CH264FrameUnpack h264packet;
while(1)
{
unsigned char* pData = NULL;
unsigned int iSize = 0;
bool isMark = false;
ProcessH264Video(pData,iSize,isMark,h264packet);
if (isMark)
{
//取一幀資料
unsigned char* pH264Frame = h264packet.GetFramePtr();
break;
}
}
}