流媒体协议.之(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);elseu32myframeLen = 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 usedptHead2->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 datamemcpy( 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;
#endifpthread_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/libmakemake install
2、simple-rtsp-server下载编译
下载后
cd simple-rtsp-servermkdir buildcd buildcmake ..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 buildcd buildcmake ..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
相关文章:
流媒体协议.之(RTP,RTCP,RTSP,RTMP,HTTP)(二)
继续上篇介绍,本篇介绍一下封装RTP的数据格式,如何将摄像头采集的码流,音频的码流,封装到rtp里,传输。 有自己私有协议例子,有rtp协议,参考代码。注意不是rtsp协议。 一、私有协议 玩过tcp协议…...
在 Kakarot ZkEVM 上使用 Starknet Scaffold 构建应用
Starknet 和 EVM 我们所知的智能合约世界一直围绕着以太坊虚拟机(EVM),其主要语言是 Solidity。 尽管 Starknet 通过 STARKs 为以太坊开辟了新的可能性,但其缺点是它有一个不同的虚拟机 (CairoVM),这要求开发者学习 …...
DBeave如何连接达梦数据库,设置达梦驱动,真酷
前言 我们在使用DBeaver连接数据库时,默认可以连接常用的数据库,如mysql数据库,postgresql数据库,oracle数据库。但是,我们的国产数据库达梦数据库,默认在IDEA里面没有驱动,所以还得配置一下才…...
2024年全球 MoonBit 编程创新赛-零基础早鸟教程-使用wasm4八小时开发井子棋小游戏
前言 本篇文章主要分享 “2024年全球 MoonBit 编程创新赛 游戏赛道”参赛过程中九宫棋游戏的开发技巧和心得。以此抛砖引玉。首先介绍下 MoonBit。 月兔语言 MoonBit 是一个用于云计算和边缘计算的 WebAssembly 端到端的编程语言工具链。 您可以访问 https://try.moonbitlang.…...
机器学习4
第3章 线性模型 3.1 线性模型的基本形式 3.1.1 线性模型的核心公式 线性模型通过属性的线性组合进行预测,其核心公式为: [ f(x) \omega_1 X_1 \omega_2 X_2 … \omega_d X_d b ] 其中: ω 1 , ω 2 , . . . , ω d \omega_1, \omega_…...
Python数值计算(33)——simpson 3/8积分公式
1. 背景知识 既然前的Simpson可以通过使用三个点构造二次曲线近似积分,那么,如果点数增加到了4个,然后不就可以构造三次多项式的曲线,实现对目标值的积分吗? 如果采用和上一节介绍的同样的方法,我们可以推…...
<项目代码>YOLOv8路面垃圾识别<目标检测>
YOLOv8是一种单阶段(one-stage)检测算法,它将目标检测问题转化为一个回归问题,能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法(如Faster R-CNN),YOLOv8具有更高的…...
Java中的注解(白金版)
Spring中常用注解 Springboot中@Validated注解的使用 Swagger中常用注解 @Validate...
actor模型
Actor模型(Actor Model)是一种用于并发计算的数学模型和编程概念,它最早由计算机科学家 Carl Hewitt 等人提出,用于简化对多线程或并发系统的设计和实现。Actor模型在并发编程、分布式系统、消息传递系统等领域具有广泛应用。 核…...
合约门合同全生命周期管理系统:企业智能合同管理的新时代
合约门合同全生命周期管理系统:企业智能合同管理的新时代 1. 引言 随着现代企业的快速发展,合同管理的复杂性日益增加。无论是采购合同、销售合同还是合作协议,合同管理已成为企业运营中至关重要的一环。传统的手工合同管理方式往往效率低下…...
vscode如何debug环境配置?torchrun与deepspeed库又该如何配置?
文章目录 前言一、vscode命令参数传递1、验证参数py文件2、第一种vscode调用方法(launch.json配置)3、第二种vscode调用方法(launch.json配置)二、deepspeed运行py文件代码(deepspeed_test.py)三、deepspeed命令调用(无法debug)四、deepspeed使用vscode进行调试(能debug)五、vs…...
Qt元对象系统 —— 信号与槽
信号与槽讨论的是Qt对象之间的连接与交互。我们就是使用这种方式实现了一个简单的异步调用。换而言之,信号与槽让我们可以不必考虑复杂的调用。只需要当我们需要在程序中表达:“希望在程序中通知一个事件而且按照我们设定的方式给出回应”的时候…...
单细胞配色效果模拟器 | 简陋版(已有颜色数组)
目的:假设你有一组颜色了,怎么模拟查看它们在单细胞DimPlot中的美学效果呢?要足够快,还要尽可能有模拟效果。 1. 尝试1: 随机矩阵,真的UMAP降维后绘图(失败) 造一个随机矩阵,使用S…...
面向对象编程中类与类之间的关系(一)
目录 1.引言 2."有一个"关系 3."是一个"关系(继承) 4.“有一个”与“是一个”的区别 5.not-a关系 6.层次结构 7.多重继承 8.混入类 1.引言 作为程序员,必然会遇到这样的情况:不同的类具有共同的特征,至少看起来彼…...
streamlit 实现 flink SQL运行界面
实现效果 streamlit flink-playground.py 文件如下: import streamlit as st import io import contextlib import sys import os import uuid import subprocess from jinja2 import Templatest.set_page_config(layout"wide")# 设置页面标题 st.title…...
鲸鱼优化算法(Whale Optimization Algorithm, WOA)原理与MATLAB例程
鲸鱼优化算法(Whale Optimization Algorithm, WOA)是一种基于鲸鱼捕食行为的智能优化算法。它模拟了座头鲸在狩猎时的“气泡网”捕食策略。 文章目录 1.适应度函数2. 更新公式2.1 突袭行为2.2 螺旋更新3.线性递减参数4. 边界处理 MATLAB 实现示例代码说明…...
MFC七段码显示实例
在MFC中添加iSenvenSegmentAnalogX控件,添加编辑框和按钮实现在编辑框中输入数字点击按钮后数字用七段码显示 1、在对话框中点击右键如下图添加控件和变量 2、在sevenDlg.h中添加代码 public: void ShowInd(int,double);3、在sevenDlg.cpp中添加代码 void CSe…...
【日常知识点】到底推不推荐用JWT?
👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主 ⛪️ 个人社区:个人社区 💞 个人主页:个人主页 🙉 专栏地址: ✅ Java 中级 🙉八股文专题:剑指大厂,手撕 J…...
网络编程项目之FTP服务器
项目介绍 模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在࿰…...
SpringBoot02:第一个springboot程序
3、第一个springboot程序 3.1、准备工作 我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。 我的环境准备: java version "…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...
《信号与系统》第 6 章 信号与系统的时域和频域特性
目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...
Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
报错 找到package.json文件 找到这个修改成 "lint": "eslint --fix --ext .js,.vue src" 为elsint有配置结尾换行符,最后运行:npm run lint --fix...
联邦学习带宽资源分配
带宽资源分配是指在网络中如何合理分配有限的带宽资源,以满足各个通信任务和用户的需求,尤其是在多用户共享带宽的情况下,如何确保各个设备或用户的通信需求得到高效且公平的满足。带宽是网络中的一个重要资源,通常指的是单位时间…...
