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;
}
}
}
我自己录制了一门讲解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标准文档。
版权声明:本文为mo4776原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。