流媒体协议.之(RTP,RTCP,RTSP,RTMP,HTTP)(二)
继续上篇介绍,本篇介绍一下封装RTP的数据格式,如何将摄像头采集的码流,音频的码流,封装到rtp里,传输。
有自己私有协议例子,有rtp协议,参考代码。注意不是rtsp协议。
一、私有协议
玩过tcp协议都知道,有这么一层关系
玩过网络开发的,应该都自己定义封装过私有协议。
我把tcp,udp,网络传输封装在另一个文件,网络传输层。然后分一个模块,私有协议,封装有效数据,数据封装层。
封装一个协议头
typedef struct MediumPrivateHead
{
SX_U32 u32Mf;
SX_U16 u16Type;
SX_U32 u32Ts;
SX_U16 u16Seqno;
SX_U32 u32Length;
SX_U8 u8Ch;
SX_U8 u8Of;
SX_U8 u8Marker;
SX_U8 u8X;
}T_MediumPrivateHead, *PT_MediumPrivateHead;
#pragma pack()
typedef enum PacketType
{
PACKET_TYPE_H264 = 118,
PACKET_TYPE_JPEG = 119,
PACKET_TYPE_AAC = 120,
PACKET_TYPE_G711 = 121,
PACKET_TYPE_H265 = 122,
PACKET_TYPE_ADPCM_DVI4 = 123,
PACKET_TYPE_G721 = 124,
PACKET_TYPE_RS422 = 125,
PACKET_TYPE_BUTT = 126,
}E_PacketType;
分片发送,数据包太大,进行分包传输,不管是什么协议,都需要调用这个函数来分包传输,传入一个bufer数据指针。
static SX_U32 MEDIUM_UDP_WriteFrame1( SX_S32 ch, SX_S8 ePacketType, void *handle, SX_S8 *ps8Buffer, SX_U32 u32Length, SX_U64 u64Pts, SX_S8 *ps8TsString )
{
PT_Medium ptTmp = (PT_Medium)handle;
SX_U32 u32Pos = 0;
SX_U32 u32LeftLen = u32Length;
SX_S8 *ps8Tmp = ps8Buffer;
int allow_send_max_len = 0;
//if(ptTmp->u32FragmentLen < 300) // len too short
//ptTmp->u32FragmentLen = 300;
allow_send_max_len = ptTmp->u32FragmentLen - sizeof(T_MediumPrivateHead);
if( u32LeftLen <= allow_send_max_len )
{
if( MEDIUM_UDP_WritePrivatePacket( ch, ePacketType, handle, ps8Tmp, u32LeftLen, u64Pts, 1, 0 ) < 0 )
return 0;
}
else
{
while( u32LeftLen > allow_send_max_len )
{
if( MEDIUM_UDP_WritePrivatePacket( ch, ePacketType, handle, ps8Tmp + u32Pos, allow_send_max_len, u64Pts, 0, 0 ) < 0 )
return 0;
u32LeftLen -= allow_send_max_len;
u32Pos += allow_send_max_len;
}
if( MEDIUM_UDP_WritePrivatePacket( ch, ePacketType, handle, ps8Tmp + u32Pos, u32LeftLen, u64Pts, 1, 0 ) < 0 )
return 0;
}
return u32Length;
}
封装私有协议包,供上面函数调用,如果是其他协议,就封装另一个函数。将有效数据和协议头封装,打包,通过udp或tcp的接口sendto发送出去
static SX_S32 MEDIUM_UDP_WritePrivatePacket( SX_S32 ch, SX_S8 ePacketType, void *handle, SX_S8 *ps8Buffer, SX_U32 u32Length, SX_U64 u64Pts, SX_U32 u32Marker, SX_U32 u32X )
{
if(ch < 0 || ch >= MEDIUM_MAX_CH)
return -1;
SX_U32 u32Pos = 0;
PT_Medium ptTmp = (PT_Medium)handle;
pthread_mutex_lock( &ptTmp->mutex);
SX_S8 *ps8Buf = ptTmp->ps8Buffer;
memset(ps8Buf ,0 ,MAX_CONFIG_FILE_LEN);
SX_U32 u32myframeLen;
if( u32Length > ptTmp->u32FragmentLen - sizeof(T_MediumPrivateHead))
u32myframeLen = ptTmp->u32FragmentLen- sizeof(T_MediumPrivateHead);
else
u32myframeLen = u32Length;
//file private head
//PT_VedioDataPkt pkthead1 = (PT_VedioDataPkt)(ps8Buf);
//PT_MediumPrivateHead ptHead2 = (PT_MediumPrivateHead)(&pkthead1->headprivate);
PT_MediumPrivateHead ptHead2 = (PT_MediumPrivateHead)(ps8Buf);
memset((SX_U8 *)ptHead2, 0, sizeof(T_MediumPrivateHead) );
ptHead2->u32Mf = (SX_U32)0x4b4e4148;
ptHead2->u16Type = (SX_U16)ePacketType;
ptHead2->u32Ts = (SX_U32)u64Pts; //not used
ptHead2->u16Seqno = ((ePacketType == PACKET_TYPE_AAC )? (SX_U16)ptTmp->u16AudioSeqno[ch] : (SX_U16)ptTmp- >u16VedioSeqno[ch]);
ptHead2->u32Length = (SX_U32)u32myframeLen;
ptHead2->u8Ch = (SX_U8)ch;
ptHead2->u8Of = (SX_U8)0x19;
ptHead2->u8Marker = (SX_U8)u32Marker;
ptHead2->u8X = (SX_U8)u32X;
u32Pos = sizeof(T_MediumPrivateHead);
//filevedio frame data
memcpy( ps8Buf + u32Pos, ps8Buffer, u32myframeLen );
//CRC32 = 0xC4C3C2C1;
*(ps8Buf + u32Pos + u32myframeLen + 0)= 0xC1;
*(ps8Buf + u32Pos + u32myframeLen + 1)= 0xC2;
*(ps8Buf + u32Pos + u32myframeLen + 2)= 0xC3;
*(ps8Buf + u32Pos + u32myframeLen + 3)= 0xC4;
if(ePacketType == PACKET_TYPE_AAC)
{
//printf("++ u16AudioSeqno %ld u32Length %ld ++\r\n",ptTmp->u16AudioSeqno[ch],sizeof(T_MediumPrivateHead) + ptHead2->u32Length + 4);
ptTmp->u16AudioSeqno[ch]++;
}
else
{
ptTmp->u16VedioSeqno[ch]++;
}
if(ptTmp->u16AudioSeqno[ch] > 65536)
ptTmp->u16AudioSeqno[ch] = 0;
if(ptTmp->u16VedioSeqno[ch] > 65536)
ptTmp->u16VedioSeqno[ch] = 0;
#endif
pthread_mutex_unlock( &ptTmp->mutex);
ts1 = get_sys_ms();
if( sendto(ptTmp->fd,
ps8Buf,
sizeof(T_MediumPrivateHead) + u32myframeLen + 4, //headlen + datalen + crclen,
0,
(struct sockaddr *)&ptTmp->other[(ePacketType == PACKET_TYPE_AAC) ? 1 : 0],
sizeof(struct sockaddr_in)) < 0 )
TRACE( DL_WARNING, "udp send failed\n" );
return 0;
}
二、 RTP协议
RTP传输音视频过程如下:
如果不按我上面私有协议传输,那就需要封装一个RTP协议,要熟悉协议格式,进行封装。
RTP报文格式
用C语言来封装,如下
取一段抓包数据
三、RTP封装视频
3.1、RTP封装H264
首先看一下H264 NALU头部定义:一个字节,是8位,按位进行划分,代表的意义。
F: 1 个比特. 一般为0 forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.
NRI: 2 个比特. nal_ref_idc. 取 00 ~ 11, 指示nalu单元的重要性,, 如 00 的 NALU 解码器可以丢弃而不影响图像的回放. 不过一般情况下不太关心这个属性.
Type: 5 个比特.nal_unit_type. 这个 NALU 单元的类型.
RTP打包原则
RTP的包长度必须要小于MTU(最大传输单元),IP协议中MTU的最大长度为1500字节。除去IP报头(20字节)、UDP报头(8字节)、RTP头(12字节),所有RTP有效载荷(即NALU内容)的长度不得超过1460字节。TCP(20字节)。上面我的例子代码有参考,分包。
RTP有三种封包模式:单一封包模式,组合封包模式,分片封包模式
这是我的协议,我所有的通信协议,都会加上这么一个起始头,用来区别本系统,发送的数据包开始字段
3.4、PES分组头部
可以看到RTP数据头,协议,非常多,没什么难度,就是多,一般我们都不会从新造轮子,网上很多大佬,开源分享,移植过来,修改就OK了,下篇,直接贴代码,封装协议,要老命,自己去手撸出来,手撸linux内核代码没必要。
找到一个博主封装的h264,封包,解包
RTP荷载H264的代码参考:
http://blog.csdn.net/dengzikun/article/details/5807694
RTP荷载PS流的代码参考:
http://www.pudn.com/downloads33/sourcecode/windows/multimedia/detail105823.html
http://www.oschina.net/code/snippet_99626_23737
这是他说的
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 start
class CH264_RTP_PACK
{
#define RTP_VERSION 2
typedef struct NAL_msg_s
{
bool eoFrame ;
unsigned char type; // NAL type
unsigned char *start; // pointer to first location in the send buffer
unsigned char *end; // pointer to last location in send buffer
unsigned long size ;
} NAL_MSG_t;
typedef struct
{
//LITTLE_ENDIAN
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 */
unsigned short seq; /* sequence number */
unsigned long ts; /* timestamp */
unsigned long ssrc; /* synchronization source */
} rtp_hdr_t;
typedef struct tagRTP_INFO
{
NAL_MSG_t nal; // NAL information
rtp_hdr_t rtp_hdr; // RTP header is assembled here
int hdr_len; // length of RTP header
unsigned char *pRTP; // pointer to where RTP packet has beem assembled
unsigned char *start; // pointer to start of payload
unsigned char *end; // pointer to end of payload
unsigned int s_bit; // bit in the FU header
unsigned int e_bit; // bit in the FU header
bool FU_flag; // fragmented NAL Unit 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)
{
}
//传入Set的数据必须是一个完整的NAL,起始码为0x00000001。
//起始码之前至少预留10个字节,以避免内存COPY操作。
//打包完成后,原缓冲区内的数据被破坏。
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 ; // skip the syncword
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 ; // skip NAL header
}
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 ;
}
//循环调用Get获取RTP包,直到返回值为NULL
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; // continue with the next RTP-FU packet
}
int bytesLeft = m_RTP_Info.nal.end - m_RTP_Info.start ;
int maxSize = m_MAXRTPPACKSIZE - 12 ; // sizeof(basic rtp header) == 12 bytes
if ( m_RTP_Info.FU_flag )
maxSize -= 2 ;
if ( bytesLeft > maxSize )
{
m_RTP_Info.end = m_RTP_Info.start + maxSize ; // limit RTP packetsize to 1472 bytes
}
else
{
m_RTP_Info.end = m_RTP_Info.start + bytesLeft ;
}
if ( m_RTP_Info.FU_flag )
{ // multiple packet NAL slice
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 ; // should be set at EofFrame
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 ;
/*!
* /n The FU indicator octet has the following format:
* /n
* /n +---------------+
* /n MSB |0|1|2|3|4|5|6|7| LSB
* /n +-+-+-+-+-+-+-+-+
* /n |F|NRI| Type |
* /n +---------------+
* /n
* /n The FU header has the following format:
* /n
* /n +---------------+
* /n |0|1|2|3|4|5|6|7|
* /n +-+-+-+-+-+-+-+-+
* /n |S|E|R| Type |
* /n +---------------+
*/
if ( m_RTP_Info.FU_flag )
{
// FU indicator F|NRI|Type
cp[12] = ( m_RTP_Info.nal.type & 0xe0 ) | 28 ; //Type is 28 for FU_A
//FU header S|E|R|Type
cp[13] = ( m_RTP_Info.s_bit << 7 ) | ( m_RTP_Info.e_bit << 6 ) | ( m_RTP_Info.nal.type & 0x1f ) ; //R = 0, must be ignored by receiver
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] ; // new start of payload
*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_PACK end
//
// class CH264_RTP_UNPACK start
class CH264_RTP_UNPACK
{
#define RTP_VERSION 2
#define BUF_SIZE (1024 * 500)
typedef struct
{
//LITTLE_ENDIAN
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 */
unsigned short seq; /* sequence number */
unsigned long ts; /* timestamp */
unsigned long ssrc; /* synchronization source */
} 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 ;
}
//pBuf为H264 RTP视频数据包,nSize为RTP视频数据包字节长度,outSize为输出视频数据帧字节长度。
//返回值为指向视频数据帧的指针。输入数据可能被破坏。
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 ;
// Check the RTP version number (it should be 2):
if ( m_RTP_Header.v != RTP_VERSION )
{
return NULL ;
}
/*
// Skip over any CSRC identifiers in the header:
if ( m_RTP_Header.cc )
{
long cc = m_RTP_Header.cc * 4 ;
if ( Size < cc )
{
return NULL ;
}
Size -= cc ;
p += cc ;
}
// Check for (& ignore) any RTP header extension
if ( m_RTP_Header.x )
{
if ( Size < 4 )
{
return NULL ;
}
Size -= 4 ;
p += 2 ;
long l = p[0] ;
l <<= 8 ;
l |= p[1] ;
p += 2 ;
l *= 4 ;
if ( Size < l ) ;
{
return NULL ;
}
Size -= l ;
p += l ;
}
// Discard any padding bytes:
if ( m_RTP_Header.p )
{
if ( Size == 0 )
{
return NULL ;
}
long Padding = p[Size-1] ;
if ( Size < Padding )
{
return NULL ;
}
Size -= Padding ;
}*/
// Check the Payload Type.
if ( m_RTP_Header.pt != m_H264PAYLOADTYPE )
{
return NULL ;
}
int PayloadType = pPayload[0] & 0x1f ;
int NALType = PayloadType ;
if ( NALType == 28 ) // FU_A
{
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 ) // SPS
{
m_bSPSFound = true ;
}
if ( !m_bSPSFound )
{
return NULL ;
}
if ( NALType == 0x07 || NALType == 0x08 ) // SPS PPS
{
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 ) // frame end
{
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 ) // KEY FRAME
{
m_wSeq = m_RTP_Header.seq ;
m_bPrevFrameEnd = false ;
return NULL ;
}
}
}
///
if ( m_RTP_Header.seq != (WORD)( m_wSeq + 1 ) ) // lost packet
{
m_wSeq = m_RTP_Header.seq ;
SetLostPacket () ;
return NULL ;
}
else
{
// 码流正常
m_wSeq = m_RTP_Header.seq ;
m_bAssemblingFrame = true ;
if ( PayloadType != 28 ) // whole NAL
{
*((DWORD*)(m_pStart)) = 0x01000000 ;
m_pStart += 4 ;
m_dwSize += 4 ;
}
else // FU_A
{
if ( pPayload[1] & 0x80 ) // FU_A start
{
*((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 // memory overflow
{
SetLostPacket () ;
return NULL ;
}
if ( m_RTP_Header.m ) // frame end
{
*outSize = m_dwSize ;
m_pStart = m_pBuf ;
m_dwSize = 0 ;
if ( NALType == 0x05 ) // KEY FRAME
{
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 ;
};
// class CH264_RTP_UNPACK end
四、推拉流测试
另一个博主封装了一个推拉流的demo,可以移植到项目中,对h264,aac,感谢博主的热心分享。
他用FFmpeg去对一个事先准备好的mp4文件,读取流,然后通过RTSP协议,推流到一个文件夹,然后写了个客户端,rtsp,拉流,播放。
如果我么要移植到ipc项目中,这里需要修改一下,将soc采集到的视频流,放到rtp包里,去掉FFmpeg解码,也不需要移植FFmpeg。
原文链接:https://blog.csdn.net/weixin_43147845/article/details/140923649
地址:https://github.com/BreakingY/simple-rtsp-client
1、准备
simple-rtsp-server依赖ffmpeg,版本要求>=4.x。支持系统:Linux
依赖安装:
sudo apt-get -y install autoconf automake build-essential libass-dev libfreetype6-dev libsdl2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo zlib1g-dev
汇编库:
sudo apt-get install yasm
sudo apt-get install nasm
视频库:
sudo apt-get install libx264-dev
sudo apt-get install libx265-dev
音频库:
sudo apt-get install libfdk-aac-dev
sudo apt-get install libmp3lame-dev
sudo apt-get install libopus-dev
ffmpeg源码下载:
wget https://ffmpeg.org//releases/ffmpeg-4.0.5.tar.bz2
tar xjvf ffmpeg-4.0.5.tar.bz2
cd ffmpeg-4.0.5
编译安装:
./configure --prefix=/usr/local --enable-libx264 --disable-x86asm --enable-nonfree --enable-libfdk-aac --enable-shared --enable-gpl --enable-libmp3lame --enable-libopus --extra-cflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib
make
make install
2、simple-rtsp-server下载编译
下载后
cd simple-rtsp-server
mkdir build
cd build
cmake ..
make -j
3、运行
cp -r ../mp4path .
./rtsp_server 0 (0-不鉴权;1-鉴权)
4、拉流测试
项目中mp4path自带了测试文件,后面把想回放的视频放到mp4path中即可
TCP拉流:
ffmpeg -rtsp_transport tcp -i "rtsp://192.168.10.17:8554/test_h264_aac.mp4" -vcodec copy -acodec copy test_h264_aac_tcp.mp4
UDP拉流:
ffmpeg -i "rtsp://192.168.10.17:8554/test_h264_aac.mp4" -vcodec copy -acodec copy test_h264_aac_udp.mp4
也可通过VLC直接播放,点击媒体->打开网络串流,输入rtsp地址即可。默认是udp拉流,要使用TCP需要打开工具->偏好设置->输入/编解码器,拉到最下方,选择“RTP over RTSP(TCP)”
他也写了一个客户端拉流,不用上面测试命令FFmpeg
二、RTSP Client实战项目
地址:https://github.com/BreakingY/simple-rtsp-client
支持RTP OVER UDP、RTP OVER TCP,支持H264/H265、AAC/PCMA、支持鉴权。
不需要任何依赖。
1、下载后编译
mkdir build
cd build
cmake ..
make -j
2、测试
./rtsp_client rtsp_url
客户端会把收到的音视频写入文件,H264/H265写入到test_out.h26x,AAC写入到test_out.aac,PCMA写入到test_out.pcma。
另一个比较厉害的,比较全
https://github.com/ireader/media-server