from:http://blog.csdn.net/dengzikun/article/details/5807694
最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包、解包的文档和代码。功夫不负有心人,找到不少有价值的文档和代码。参考这些资料,写了H264 RTP打包类、解包类,实现了单个NAL单元包和FU_A分片单元包。对于丢包处理,采用简单的策略:丢弃随后的所有数据包,直到收到关键帧。测试效果还不错,代码贴上来,若能为同道中人借鉴一二,足矣。两个类的使用说明如下(省略了错误处理过程):
DWORD H264SSRC ;
CH264_RTP_PACK pack ( H264SSRC ) ;
BYTE *pVideoData ;
DWORD Size, ts ;
bool IsEndOfFrame ;
WORD wLen ;
pack.Set ( pVideoData, Size, ts, IsEndOfFrame ) ;
BYTE *pPacket ;
while ( pPacket = pack.Get ( &wLen ) )
{
// rtp packet process
// ...
}
HRESULT hr ;
CH264_RTP_UNPACK unpack ( hr ) ;
BYTE *pRtpData ;
WORD inSize;
int outSize ;
BYTE *pFrame = unpack.Parse_RTP_Packet ( pRtpData, inSize, &outSize ) ;
if ( pFrame != NULL )
{
// frame process
// ...
}
-
- class CH264_RTP_PACK
- {
- #define RTP_VERSION 2
-
- typedef struct NAL_msg_s
- {
- bool eoFrame ;
- unsigned char type;
- unsigned char *start;
- unsigned char *end;
- unsigned long size ;
- } NAL_MSG_t;
-
- typedef struct
- {
-
- unsigned short cc:4;
- unsigned short x:1;
- unsigned short p:1;
- unsigned short v:2;
- unsigned short pt:7;
- unsigned short m:1;
-
- unsigned short seq;
- unsigned long ts;
- unsigned long ssrc;
- } rtp_hdr_t;
-
- typedef struct tagRTP_INFO
- {
- NAL_MSG_t nal;
- rtp_hdr_t rtp_hdr;
- int hdr_len;
-
- unsigned char *pRTP;
- unsigned char *start;
- unsigned char *end;
-
- unsigned int s_bit;
- unsigned int e_bit;
- bool FU_flag;
- } RTP_INFO;
-
- public:
- CH264_RTP_PACK(unsigned long H264SSRC, unsigned char H264PAYLOADTYPE=96, unsigned short MAXRTPPACKSIZE=1472 )
- {
- m_MAXRTPPACKSIZE = MAXRTPPACKSIZE ;
- if ( m_MAXRTPPACKSIZE > 10000 )
- {
- m_MAXRTPPACKSIZE = 10000 ;
- }
- if ( m_MAXRTPPACKSIZE < 50 )
- {
- m_MAXRTPPACKSIZE = 50 ;
- }
-
- memset ( &m_RTP_Info, 0, sizeof(m_RTP_Info) ) ;
-
- m_RTP_Info.rtp_hdr.pt = H264PAYLOADTYPE ;
- m_RTP_Info.rtp_hdr.ssrc = H264SSRC ;
- m_RTP_Info.rtp_hdr.v = RTP_VERSION ;
-
- m_RTP_Info.rtp_hdr.seq = 0 ;
- }
-
- ~CH264_RTP_PACK(void)
- {
- }
-
-
-
-
- bool Set ( unsigned char *NAL_Buf, unsigned long NAL_Size, unsigned long Time_Stamp, bool End_Of_Frame )
- {
- unsigned long startcode = StartCode(NAL_Buf) ;
-
- if ( startcode != 0x01000000 )
- {
- return false ;
- }
-
- int type = NAL_Buf[4] & 0x1f ;
- if ( type < 1 || type > 12 )
- {
- return false ;
- }
-
- m_RTP_Info.nal.start = NAL_Buf ;
- m_RTP_Info.nal.size = NAL_Size ;
- m_RTP_Info.nal.eoFrame = End_Of_Frame ;
- m_RTP_Info.nal.type = m_RTP_Info.nal.start[4] ;
- m_RTP_Info.nal.end = m_RTP_Info.nal.start + m_RTP_Info.nal.size ;
-
- m_RTP_Info.rtp_hdr.ts = Time_Stamp ;
-
- m_RTP_Info.nal.start += 4 ;
-
- if ( (m_RTP_Info.nal.size + 7) > m_MAXRTPPACKSIZE )
- {
- m_RTP_Info.FU_flag = true ;
- m_RTP_Info.s_bit = 1 ;
- m_RTP_Info.e_bit = 0 ;
-
- m_RTP_Info.nal.start += 1 ;
- }
- else
- {
- m_RTP_Info.FU_flag = false ;
- m_RTP_Info.s_bit = m_RTP_Info.e_bit = 0 ;
- }
-
- m_RTP_Info.start = m_RTP_Info.end = m_RTP_Info.nal.start ;
- m_bBeginNAL = true ;
-
- return true ;
- }
-
-
- unsigned char* Get ( unsigned short *pPacketSize )
- {
- if ( m_RTP_Info.end == m_RTP_Info.nal.end )
- {
- *pPacketSize = 0 ;
- return NULL ;
- }
-
- if ( m_bBeginNAL )
- {
- m_bBeginNAL = false ;
- }
- else
- {
- m_RTP_Info.start = m_RTP_Info.end;
- }
-
- int bytesLeft = m_RTP_Info.nal.end - m_RTP_Info.start ;
- int maxSize = m_MAXRTPPACKSIZE - 12 ;
- if ( m_RTP_Info.FU_flag )
- maxSize -= 2 ;
-
- if ( bytesLeft > maxSize )
- {
- m_RTP_Info.end = m_RTP_Info.start + maxSize ;
- }
- else
- {
- m_RTP_Info.end = m_RTP_Info.start + bytesLeft ;
- }
-
- if ( m_RTP_Info.FU_flag )
- {
- if ( m_RTP_Info.end == m_RTP_Info.nal.end )
- {
- m_RTP_Info.e_bit = 1 ;
- }
- }
-
- m_RTP_Info.rtp_hdr.m = m_RTP_Info.nal.eoFrame ? 1 : 0 ;
- if ( m_RTP_Info.FU_flag && !m_RTP_Info.e_bit )
- {
- m_RTP_Info.rtp_hdr.m = 0 ;
- }
-
- m_RTP_Info.rtp_hdr.seq++ ;
-
- unsigned char *cp = m_RTP_Info.start ;
- cp -= ( m_RTP_Info.FU_flag ? 14 : 12 ) ;
- m_RTP_Info.pRTP = cp ;
-
- unsigned char *cp2 = (unsigned char *)&m_RTP_Info.rtp_hdr ;
- cp[0] = cp2[0] ;
- cp[1] = cp2[1] ;
-
- cp[2] = ( m_RTP_Info.rtp_hdr.seq >> 8 ) & 0xff ;
- cp[3] = m_RTP_Info.rtp_hdr.seq & 0xff ;
-
- cp[4] = ( m_RTP_Info.rtp_hdr.ts >> 24 ) & 0xff ;
- cp[5] = ( m_RTP_Info.rtp_hdr.ts >> 16 ) & 0xff ;
- cp[6] = ( m_RTP_Info.rtp_hdr.ts >> 8 ) & 0xff ;
- cp[7] = m_RTP_Info.rtp_hdr.ts & 0xff ;
-
- cp[8] = ( m_RTP_Info.rtp_hdr.ssrc >> 24 ) & 0xff ;
- cp[9] = ( m_RTP_Info.rtp_hdr.ssrc >> 16 ) & 0xff ;
- cp[10] = ( m_RTP_Info.rtp_hdr.ssrc >> 8 ) & 0xff ;
- cp[11] = m_RTP_Info.rtp_hdr.ssrc & 0xff ;
- m_RTP_Info.hdr_len = 12 ;
-
- if ( m_RTP_Info.FU_flag )
- {
-
- cp[12] = ( m_RTP_Info.nal.type & 0xe0 ) | 28 ;
-
- cp[13] = ( m_RTP_Info.s_bit << 7 ) | ( m_RTP_Info.e_bit << 6 ) | ( m_RTP_Info.nal.type & 0x1f ) ;
-
- m_RTP_Info.s_bit = m_RTP_Info.e_bit= 0 ;
- m_RTP_Info.hdr_len = 14 ;
- }
- m_RTP_Info.start = &cp[m_RTP_Info.hdr_len] ;
-
- *pPacketSize = m_RTP_Info.hdr_len + ( m_RTP_Info.end - m_RTP_Info.start ) ;
- return m_RTP_Info.pRTP ;
- }
-
- private:
- unsigned int StartCode( unsigned char *cp )
- {
- unsigned int d32 ;
- d32 = cp[3] ;
- d32 <<= 8 ;
- d32 |= cp[2] ;
- d32 <<= 8 ;
- d32 |= cp[1] ;
- d32 <<= 8 ;
- d32 |= cp[0] ;
- return d32 ;
- }
-
- private:
- RTP_INFO m_RTP_Info ;
- bool m_bBeginNAL ;
- unsigned short m_MAXRTPPACKSIZE ;
- };
-
-
-
-
- class CH264_RTP_UNPACK
- {
-
- #define RTP_VERSION 2
- #define BUF_SIZE (1024 * 500)
-
- typedef struct
- {
-
- unsigned short cc:4;
- unsigned short x:1;
- unsigned short p:1;
- unsigned short v:2;
- unsigned short pt:7;
- unsigned short m:1;
-
- unsigned short seq;
- unsigned long ts;
- unsigned long ssrc;
- } rtp_hdr_t;
- public:
-
- CH264_RTP_UNPACK ( HRESULT &hr, unsigned char H264PAYLOADTYPE = 96 )
- : m_bSPSFound(false)
- , m_bWaitKeyFrame(true)
- , m_bPrevFrameEnd(false)
- , m_bAssemblingFrame(false)
- , m_wSeq(1234)
- , m_ssrc(0)
- {
- m_pBuf = new BYTE[BUF_SIZE] ;
- if ( m_pBuf == NULL )
- {
- hr = E_OUTOFMEMORY ;
- return ;
- }
-
- m_H264PAYLOADTYPE = H264PAYLOADTYPE ;
- m_pEnd = m_pBuf + BUF_SIZE ;
- m_pStart = m_pBuf ;
- m_dwSize = 0 ;
- hr = S_OK ;
- }
-
- ~CH264_RTP_UNPACK(void)
- {
- delete [] m_pBuf ;
- }
-
-
-
- BYTE* Parse_RTP_Packet ( BYTE *pBuf, unsigned short nSize, int *outSize )
- {
- if ( nSize <= 12 )
- {
- return NULL ;
- }
-
- BYTE *cp = (BYTE*)&m_RTP_Header ;
- cp[0] = pBuf[0] ;
- cp[1] = pBuf[1] ;
-
- m_RTP_Header.seq = pBuf[2] ;
- m_RTP_Header.seq <<= 8 ;
- m_RTP_Header.seq |= pBuf[3] ;
-
- m_RTP_Header.ts = pBuf[4] ;
- m_RTP_Header.ts <<= 8 ;
- m_RTP_Header.ts |= pBuf[5] ;
- m_RTP_Header.ts <<= 8 ;
- m_RTP_Header.ts |= pBuf[6] ;
- m_RTP_Header.ts <<= 8 ;
- m_RTP_Header.ts |= pBuf[7] ;
-
- m_RTP_Header.ssrc = pBuf[8] ;
- m_RTP_Header.ssrc <<= 8 ;
- m_RTP_Header.ssrc |= pBuf[9] ;
- m_RTP_Header.ssrc <<= 8 ;
- m_RTP_Header.ssrc |= pBuf[10] ;
- m_RTP_Header.ssrc <<= 8 ;
- m_RTP_Header.ssrc |= pBuf[11] ;
-
- BYTE *pPayload = pBuf + 12 ;
- DWORD PayloadSize = nSize - 12 ;
-
-
- if ( m_RTP_Header.v != RTP_VERSION )
- {
- return NULL ;
- }
-
-
-
-
- if ( m_RTP_Header.pt != m_H264PAYLOADTYPE )
- {
- return NULL ;
- }
-
- int PayloadType = pPayload[0] & 0x1f ;
- int NALType = PayloadType ;
- if ( NALType == 28 )
- {
- if ( PayloadSize < 2 )
- {
- return NULL ;
- }
-
- NALType = pPayload[1] & 0x1f ;
- }
-
- if ( m_ssrc != m_RTP_Header.ssrc )
- {
- m_ssrc = m_RTP_Header.ssrc ;
- SetLostPacket () ;
- }
-
- if ( NALType == 0x07 )
- {
- m_bSPSFound = true ;
- }
-
- if ( !m_bSPSFound )
- {
- return NULL ;
- }
-
- if ( NALType == 0x07 || NALType == 0x08 )
- {
- m_wSeq = m_RTP_Header.seq ;
- m_bPrevFrameEnd = true ;
-
- pPayload -= 4 ;
- *((DWORD*)(pPayload)) = 0x01000000 ;
- *outSize = PayloadSize + 4 ;
- return pPayload ;
- }
-
- if ( m_bWaitKeyFrame )
- {
- if ( m_RTP_Header.m )
- {
- m_bPrevFrameEnd = true ;
- if ( !m_bAssemblingFrame )
- {
- m_wSeq = m_RTP_Header.seq ;
- return NULL ;
- }
- }
-
- if ( !m_bPrevFrameEnd )
- {
- m_wSeq = m_RTP_Header.seq ;
- return NULL ;
- }
- else
- {
- if ( NALType != 0x05 )
- {
- m_wSeq = m_RTP_Header.seq ;
- m_bPrevFrameEnd = false ;
- return NULL ;
- }
- }
- }
-
-
-
- if ( m_RTP_Header.seq != (WORD)( m_wSeq + 1 ) )
- {
- m_wSeq = m_RTP_Header.seq ;
- SetLostPacket () ;
- return NULL ;
- }
- else
- {
-
-
- m_wSeq = m_RTP_Header.seq ;
- m_bAssemblingFrame = true ;
-
- if ( PayloadType != 28 )
- {
- *((DWORD*)(m_pStart)) = 0x01000000 ;
- m_pStart += 4 ;
- m_dwSize += 4 ;
- }
- else
- {
- if ( pPayload[1] & 0x80 )
- {
- *((DWORD*)(m_pStart)) = 0x01000000 ;
- m_pStart += 4 ;
- m_dwSize += 4 ;
-
- pPayload[1] = ( pPayload[0] & 0xE0 ) | NALType ;
-
- pPayload += 1 ;
- PayloadSize -= 1 ;
- }
- else
- {
- pPayload += 2 ;
- PayloadSize -= 2 ;
- }
- }
-
- if ( m_pStart + PayloadSize < m_pEnd )
- {
- CopyMemory ( m_pStart, pPayload, PayloadSize ) ;
- m_dwSize += PayloadSize ;
- m_pStart += PayloadSize ;
- }
- else
- {
- SetLostPacket () ;
- return NULL ;
- }
-
- if ( m_RTP_Header.m )
- {
- *outSize = m_dwSize ;
-
- m_pStart = m_pBuf ;
- m_dwSize = 0 ;
-
- if ( NALType == 0x05 )
- {
- m_bWaitKeyFrame = false ;
- }
- return m_pBuf ;
- }
- else
- {
- return NULL ;
- }
- }
- }
-
- void SetLostPacket()
- {
- m_bSPSFound = false ;
- m_bWaitKeyFrame = true ;
- m_bPrevFrameEnd = false ;
- m_bAssemblingFrame = false ;
- m_pStart = m_pBuf ;
- m_dwSize = 0 ;
- }
-
- private:
- rtp_hdr_t m_RTP_Header ;
-
- BYTE *m_pBuf ;
-
- bool m_bSPSFound ;
- bool m_bWaitKeyFrame ;
- bool m_bAssemblingFrame ;
- bool m_bPrevFrameEnd ;
- BYTE *m_pStart ;
- BYTE *m_pEnd ;
- DWORD m_dwSize ;
-
- WORD m_wSeq ;
-
- BYTE m_H264PAYLOADTYPE ;
- DWORD m_ssrc ;
- };
-
23楼 2014-11-07 16:31发表-
- 调用博主的代码成功实现了对RTP包的解析和帧重组,直接保存成文件可以用VLC播放。另,博主的解包类里面似乎没有对SEI进行处理,所以在转换某个摄像机的视频时,我得到的数据全都是SPS与PPS,后查看直接保存的二进制文件,发现因为SEI也是有序列号的,不加1的话,下一次比较定会出错。已手动修改。
22楼 2014-07-09 09:40发表-
- 你好,非常感谢你的代码,我调试可以正常解包的。 但是我现在有个问题,UDP传输,我本机发送本机接收,总是断断续续的丢包, if ( m_RTP_Header.seq != (WORD)( m_wSeq + 1 ) )//lost packet 会进入到这个判断里面?百思不得解,希望博主提示一下哈。 谢谢
Re: 2014-07-09 23:20发表-
- 回复chen495810242:本机收发丢包,可能是收发速率不匹配,可以增大 socket缓冲区试试。试试把接收数据后的处理逻辑去掉,只接收数据。
Re: 2014-07-10 08:53发表-
- 回复dengzikun:额,我必须表示感谢,增加接收缓冲区大小就可以了,⊙﹏⊙b汗。 在麻烦一下,你还有其他组包模式的解析方式吗? FU-B,STAP-A,STAP-B等,还有你说的错误处理省略了,能否也加上,我觉得这个其实更重要,非常感谢
Re: 2014-07-11 14:26发表-
- 回复chen495810242:"省略错误处理"是指博文中的类使用示例代码。这两个类是可以放心使用的。H264 RTP包的其他解析方式你可以参考live555等开源库。
Re: 2014-07-16 11:12发表-
- 回复dengzikun:如果是大端模式怎么处理啊?谢谢
Re: 2014-07-16 11:32发表-
- 回复chen495810242:typedef struct { #ifdef _BIG_ENDIAN unsigned short v:2; /* packet type */ unsigned short p:1; /* padding flag */ unsigned short x:1; /* header extension flag */ unsigned short cc:4; /* CSRC count */ unsigned short m:1; /* marker bit */ unsigned short pt:7; /* payload type */ #else unsigned short cc:4; /* CSRC count */ unsigned short x:1; /* header extension flag */ unsigned short p:1; /* padding flag */ unsigned short v:2; /* packet type */ unsigned short pt:7; /* payload type */ unsigned short m:1; /* marker bit */ #endif uint16_t seq; /* sequence number */ uint32_t ts; /* timestamp */ uint32_t ssrc; /* synchronization source */ } rtp_hdr_t;
21楼 2014-06-09 17:33发表-
- 楼主,最近在做RTP包解析,发现用你这个解包不能成功啊。解包的时候,第一帧是I帧,SPS部分没有解析成功,然后每个包打包的多余字节没有去掉
Re: 2014-06-09 19:43发表-
- 回复nanqingzhe:代码片段只能解析单个NAL单元包和FU_A分片单元包,请确认你的H264 RTP打包格式。
Re: 2014-06-11 10:29发表-
- 回复dengzikun:打包解包均采用你给的代码。 一个完整的I帧塞进去打包,打包得到的数据交由解包的代码。
Re: 2014-06-11 10:27发表-
- 回复dengzikun:我用的就是你这个打包代码。将一个完整的I帧塞进去打包,打包后的数据交给你的这个解包代码。
Re: 2014-06-12 23:12发表-
- 回复nanqingzhe:你看一下Set 和Parse_RTP_Packet 的使用说明,应该是用法的问题。
20楼 2012-11-26 15:57发表-
- rtp_hdr_t结构体与标准的顺序不一样
Re: 2012-11-26 18:46发表-
- 回复huai_f:代码中注明了是little endian。
Re: 2012-11-28 01:13发表-
- 回复dengzikun:嗯,我错了,我在jrtplib库中也看到过
19楼 2012-06-29 14:16发表-
- 非常感谢谢!!!基于时间戳这一块,困扰我大半个月。。目前圆满解决。我采用的方式跟你的类似,再次谢谢您。 memcpy(dst, nal[0].p_payload, i_frame_size); if (count > 1) { struct timeval now; gettimeofday(&now, NULL); double val = (now.tv_sec - firstTime.tv_sec) + (now.tv_usec - firstTime.tv_usec) / 1000000.0; ts_current= ts_current + val * 90000.0 + 0.5; firstTime = now; } else { ts_current= 0; gettimeofday(&firstTime, NULL); } count ++;
18楼 2012-06-27 18:14发表-
- 有没有pseudo code?? demo 一下?这一块我一直没搞清楚在fps变化的情况下。
Re: 2012-06-29 09:12发表-
- 回复chinapacs:class RTP_Timestamp { public: RTP_Timestamp(DWORD unit) : m_dwUnit(unit) { QueryPerformanceFrequency ( (LARGE_INTEGER*)&m_Freq ) ; } ~RTP_Timestamp(void) { } DWORD GetTime () { __int64 current ; QueryPerformanceCounter ( (LARGE_INTEGER*)¤t ) ; DWORD ts = current * m_dwUnit / m_Freq ; return ts ; } private: DWORD m_dwUnit ; __int64 m_Freq ; }; RTP_Timestamp TS ( 90000 ) ; 每一帧调用TS.GetTime()获取时间戳。