1. 程式人生 > >實現對rtp H264碼流的組幀

實現對rtp H264碼流的組幀

rtp打包h264,包含了三種類型的包:

  1.  一個rtp包攜帶了一幀資料(single)
  2.  多個rtp包攜帶了一幀資料(FU-A)
  3.  一個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;
        }
    }

}