实现对rtp H264码流的组帧

  • Post author:
  • Post category:其他


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;
        }
    }

}

我自己录制了一门讲解RTP协议的课程,主要讲解的是RTP for H264/265。对自己算是对知识点的一个总结吧,也顺便”知识变现”一把,望大家多多捧场。如果看了课程后,有什么疑问可以在评论区留言,我会及时解答处理。课程包括如下内容:

1. 通过wireshark分析RTP码流

2. 通过wireshark分析H264码流

3. 讲解H264/265的FU-A 的封包模式

4. 通过代码讲解了如何对H264/265 FU-A模式的封包进行拆包组帧

5. 通过实现一个pcap 码流提取工具对组帧及帧的分析进行综合讲解

课件中包括了实例的rtp抓包文件及rtp for h264/265的rfc标准文档。


51CTO链接



版权声明:本文为mo4776原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。