流媒体协议.之(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 "…...
快速入门HTML
欢迎关注个人主页:逸狼 创造不易,可以点点赞吗 如有错误,欢迎指出~ 目录 第一个html文件 标签 h1~h6 p >段落标签 br > 换行标签 img >图片标签 a >超链接标签 表格标签 表单标签 表单控件 form表单 ⽆语义标签:div&span 综…...
RabbitMQ是一个开源的消息代理和队列服务器
RabbitMQ是一个开源的消息代理和队列服务器,它基于AMQP(Advanced Message Queuing Protocol,高级消息队列协议)协议实现,同时也支持其他消息协议如STOMP、MQTT等。作为一个可靠的消息传递服务,RabbitMQ在分…...
经典算法思想--并查集
前言 (最近在学习Java,所有函数都是用Java语言来书写的)前言部分是一些前提储备知识 在并查集(Union-Find)数据结构中,rank(中文称为“秩”)是用来表示树的高度或深度的一种辅助信息…...
挑战Java面试题复习第2天,百折不挠
挑战第 2 天 ArrayList和linkedList的区别HashMap和HashTable的区别Collection 与 Collections 的区别Java的四种引用泛型常用特点 ArrayList和linkedList的区别 底层数据结构: ArrayList:基于动态数组实现,支持快速随机访问。LinkedList&a…...
【vue之道】
vue之道 1. 一生二,二生万物思想2. 变化之律3. 变化之实在哪?4.而后学于形乃已!4.1 展示之形变4.2 动之气谓之指令4.3 血之养分的载体,于vue之绑定载具4.4 vue之道(万法规一篇) 1. 一生二,二生万…...
基于麻雀优化算法SSA的CEEMDAN-BiLSTM-Attention的预测模型
往期精彩内容: 时序预测:LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较 全是干货 | 数据集、学习资料、建模资源分享! EMD、EEMD、FEEMD、CEEMD、CEEMDAN的区别、原理和Python实现(一)EMD-CSDN博客 EMD、EEM…...
Linux:指令再认识
文章目录 前言一、知识点1. Linux下一切皆文件,也就是说显示器也是一种文件2. 指令是什么?3. ll 与 ls -l4. 日志5. 管道6. 时间戳 二、基本指令1. man指令2. cp指令3. mv指令4. 查看文件1)cat/tac指令——看小文件2)more/less指令…...
PHP如何抛出和接收错误
在PHP中,抛出和接收错误通常涉及异常处理机制,以及错误和异常的处理函数。以下是如何在PHP中抛出和接收错误的详细指南: 抛出错误(异常) 在PHP中,你可以使用throw关键字来抛出一个异常。这通常在你检测到…...
计算机网络:网络层 —— IPv4 地址的应用规划
文章目录 IPv4地址的应用规划定长的子网掩码变长的子网掩码 IPv4地址的应用规划 IPv4地址的应用规划是指将给定的 IPv4地址块 (或分类网络)划分成若干个更小的地址块(或子网),并将这些地址块(或子网)分配给互联网中的不同网络,进而可以给各网络中的主机…...
Mongodb命令大全
Mongodb命令大全 一、数据库相关命令二、集合相关命令三、文档(数据)相关命令1、_id 字段说明2、查询2.1、 查询操作符2.2、内嵌文档查询2.3、数组文档查询2.4、去重查询2.5、查询排序 sort2.6、分页查询2.7、指定列投影查询返回2.8、查询统计个数 count 3、聚合查询3.1、查询用…...
wordpress 页脚改颜色/小程序开发收费价目表
cookie的实现很简单,在服务器返回的响应中,header里增加Set-Cookie,浏览器接受到Set-Cookie中的value,下次访问这个网站的请求中就会带上这个cookie。编写一个增加cookie的函数: def add_cookie(cookie):header HTTP…...
wordpress 时间轴插件/太原关键词排名优化
题目描述: 给定两个整型数组A和B。我们将A和B中的元素两两相加可以得到数组C。譬如A为[1,2],B为[3,4].那么由A和B中的元素两两相加得到的数组C为[4,5,5,6]。现在给你数组A和B,求由A和B两两相加得到的数组C中,第K小的数字。 思路 1…...
优化网站和网站建设/昆山网站建设公司
CMPSC-121作业代做、代写C/C语言作业、代做Programming Techniques作业、代写C/C程序设计作业CMPSC-121: Intro to Programming Techniques (Fall 2018)Project 2 - Fall 2018 (100 points)Due Sunday, October 28 at 11:59pmObjectivesAfter this project, students should be…...
免费空间分享/百度seo排名查询
转载 https://blog.csdn.net/liangdaojun/article/details/97784967 安装 如果不想安装 Anaconda,可以跳过这一步。因为现在深度学习库更新的比较频繁,对于同一个库,我们有可能使用多个版本,比如tensorflow-gpu1.0.0 , …, tensor…...
跟有流量的网站做友情链接/今日头条号官网
说明: (1)在本专栏中,我们会通过开发【Spring Cloud开发课程查询项目】来介绍Spring Cloud;其中,具体的项目业务内容不是重点,重点是Spring Cloud的开发和各组件的使用套路; 一&…...
wordpress无法评论/seo网站优化方法
项目背景和意义 目的:本课题主要目标是设计并能够实现一个基于微信小程序运动场地预约系统,前台用户使用小程序,后台管理使用基PHPMySql的B/S架构;通过后台添加开放的场地类型(比如羽毛球、篮球、网球等)、…...