一、简介
1.1 RTP
RTP全名是Real-time Transport Protocol(实时传输协议)。它是IETF提出的一个标准,对应的RFC文档为RFC3550(RFC1889为其过期版本)。RFC3550不仅定义了RTP,而且定义了配套的相关协议RTCP(Real-time Transport Control Protocol,即实时传输控制协议)。RTP用来为IP网上的语音、图像、传真等多种需要实时传输的多媒体数据提供端到端的实时传输服务。RTP为Internet上端到端的实时传输提供时间信息和流同步,但并不保证服务质量,服务质量由RTCP来提供。
1.2 RTP 使用场景
RTP协议详细说明了在互联网上传递音频和视频的标准数据包格式,通常用于需要传输音视频流的场景中。诸如在线课堂,在线会议,音视频通话。RTP 本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于低层服务去实现这一过程。RTP 并不保证传送或防止无序传送,也不确定底层网络的可靠性。RTP 实行有序传送, RTP 中的序列号允许接收方重组发送方的包序列,同时序列号也能用于决定适当的包位置。
二、RTP 详解
2.1 在网络体系中的层次
从开发者角度来讲RTP 是位于TCP/UDP 之上的应用层协议,因为RTP的实现还是要靠开发者自己,因而可以看成应用层协议也不为不妥。RTP实现者在发送RTP数据时,需先将数据封装成RTP包,而在接收到RTP数据包,需要将数据从RTP包中提取出来。
2.2 RTP 会话过程
当应用程序建立一个RTP会话时,应用程序将确定一对目的传输地址。目的传输地址由一个网络地址和一对端口组成,有两个端口:一个给RTP包,一个给RTCP包,使得RTP/RTCP数据能够正确发送。RTP数据发向偶数的UDP端口,而对应的控制信号RTCP数据发向相邻的奇数UDP端口(偶数的UDP端口+1),这样就构成一个UDP端口对。 RTP的发送过程如下,接收过程则相反。
- RTP协议从上层接收流媒体信息码流(如H.264),封装成RTP数据包;RTCP从上层接收控制信息,封装成RTCP控制包。
- RTP将RTP 数据包发往UDP端口对中偶数端口;RTCP将RTCP控制包发往UDP端口对中的接收端口
2.3 RTP 组包与收包
2.3.1 RTP 协议头
任何一个协议都包含发送端和解析端,发送端按照协议格式进行数据组包发送,接收端按照协议格式接收再解包。协议如何组包首先得先知道其协议格式,每个字节代表的含义,然后按照格式填充数据发送即可。接收端按照协议再进行数据的拆包,拿到想要的数据即完成一次数据的传输。RTP 固定头格式如下:
- 版本号(V):2比特,用来标志使用的RTP版本。
- 填充位(P):1比特,如果该位置位,则该RTP包的尾部就包含附加的填充字节。
- 扩展位(X):1比特,如果该位置位的话,RTP固定头部后面就跟有一个扩展头部。
- CSRC计数器(CC):4比特,含有固定头部后面跟着的CSRC的数目。
- 标记位(M):1比特,该位的解释由配置文档(Profile)来承担.
- 不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。 由于音频帧比较小,一个RTP包就是一个音频帧,所以该位直接置1
- 视频,有些帧特别大已经超过了TCP/UDP 的MTU,所以需要对当前帧进行分片。如下几种情况需要置为1,意味着本帧结束
- sps pps 帧 为1
- 单帧包 为1
- 分片包/组合包 最后一帧为1,其他为0
- 载荷类型(PT):7比特,标识了RTP载荷的类型,通常在这里区分是负载音频还是视频数据。
- 序列号(SN):16比特,发送方在每发送完一个RTP包后就将该域的值增加1,接收方可以由该域检测包的丢失及恢复包序列。序列号的初始值是随机的。
- 时间戳:32比特,记录了该包中数据的第一个字节的采样时刻。在一次会话开始时,时间戳初始化成一个初始值。即使在没有信号发送时,时间戳的数值也要随时间而不断地增加(时间在流逝嘛)。时间戳是去除抖动和实现同步不可缺少的。
- 同步源标识符(SSRC):32比特,同步源就是指RTP包流的来源。在同一个RTP会话中不能有两个相同的SSRC值。该标识符是随机选取的 RFC1889推荐了MD5随机算法。
- 贡献源列表(CSRC List):0~15项,每项32比特,用来标志对一个RTP混合器产生的新包有贡献的所有RTP包的源。由混合器将这些有贡献的SSRC标识符插入表中。SSRC标识符都被列出来,以便接收端能正确指出交谈双方的身份。
2.3.2 组包的核心代码如下
/**
* 通过 rtp 发送 数据包,包组织结构
* 单个视频包: rtp head + 去掉h264 起始码的NALU
* 分片:[rtp head] [FU indicator] [FU head]
* @param prefixData [FU indicator] [FU head] 各1 个字节,如果有,则填上,没有可不填
* @param data nalu 数据
* @param offset
* @param size
* @param mark M 标志,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
* 由于音频帧比较小,一个RTP包就是一个音频帧,所以该位直接置1
* 视频,如下几种情况需要置为1,意味着本帧结束
* 1.sps pps 帧 为1
* 2.单帧包 为1
* 3.分片包/组合包 最后一帧为1,其他为0
*
* @param seqNum 序列号
* @param timeUs 时间戳
* @throws IOException
*/
public void addPacket(byte[] prefixData, byte[] data, int offset, int size, int mark,short seqNum,long timeUs) throws IOException {
/*
RTP packet header
Bit offset[b] 0-1 2 3 4-7 8 9-15 16-31
0 Version P X CC M PT Sequence Number 31
32 Timestamp 63
64 SSRC identifier 95
---------------------------------------------------------------------------------------
|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
---------------------------------------------------------------------------------------
|v |p|x|cc-----|m|-------pt7bit------|------------------seq 16bit--------------------|
---------------------------------------------------------------------------------------
|-----------------------time stamp 32 bit---------------------------------------------|
---------------------------------------------------------------------------------------
|------------------------------ssrc 32bit---------------------------------------------|
*/
ByteBuffer buffer = ByteBuffer.allocate(500000);
//存放v p x cc 1 字节
buffer.put((byte)(2 << 6));
//存放 m、 pt 1 个字节 96 = 0x 0110 0000
// m = 1 pt = 100 0000
// mark << 7 = x000 0000 得到mark 标志值
// 再与 payloadType | 操作 得到一个字节的 m_payloadType
buffer.put((byte)((mark<< 7) | payloadType));
//序列号 16 位 2 字节
buffer.putShort(seqNum);
//时间戳 4字节
buffer.putInt((int)(timeUs));
//同步信源 ssrc 4字节SSRC相当于一个RTP传输session的ID,就象每个人都有一个名字一样,
//每一个RTP传输也都有一个名字。这个数字是随机产生,并且要保证唯一。当RTP session改变(如IP等)时,这个ID也要改变
buffer.putInt(getSsrc());
//提供csrc 标识符 由前面的cc 指定
//buffer.putInt(size);
if (prefixData != null) {
buffer.put(prefixData);
}
buffer.put(data, offset, size);
sendPacket(buffer, buffer.position());
}
2.3.3 收包核心代码
private boolean dumpRtp(byte[] data, int size) {
if (data != null) {
//分析12字节的头部数据
//读取1 字节 得到 v p x cc
int v = (data[0] & 0xc0) >> 6;
int p = (data[0] & 0x20) >> 5;
int x = (data[0] & 0x10) >> 4;
int cc = data[0] & 0x0f;
//读取第2字节的第1位 得到 mark 标志
int mark = (data[1] & 0x80) >> 7;
//读取 第2 字节的后7位得到payloadType 用来区分 流类型
int payLoadType = data[1] & 0x7f;
//读取2字节 得到序列号
int sequenceNumber = (data[2] << 8 & 0xff00) | (data[3] & 0x00ff);
//读取 4 byte 得到序列号
int timeStamp = (data[4] << 24 & 0xff000000) | (data[5] << 16 & 0x00ff0000) | (data[6] << 8 & 0x0000ff00)
| (data[7] & 0x000000ff);
//同步信源 ssrc 4字节SSRC
int ssrc = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11];
//LogUtils.v("106--------:mark = " + mark + " payloadType :" +
// payLoadType + " seqNum:" + sequenceNumber + " timeStamp:" + timeStamp + " size: " + size);
if (payLoadType == VIDEO_TYPE) {
if(size <= mtu int nalustartcodeindex='0;' ifdata12='= 0x00' data13='= 0x00' data14='= 0x01)' nalustartcodeindex='3;' ifdata12='= 0x00' data13='= 0x00' data14='= 0x00' data15='= 0x01)' nalustartcodeindex='4;' int nalutype='data[12' nalustartcodeindex 0x1f logutils.v141--------00 00 01 : nalutype nalutype mark: mark ifnalutype='= FRAGMENT_UNIT_A_TYPE)' int s='(data[13]' 0x80>> 7;
int e = (data[13] & 0x40) >> 6;
int frameType = data[13] & 0x1f;
LogUtils.v("141-----------分片包的最后一组数据: s = " + s +" e:" + e + " frameType :" + frameType);
//分片结束,可以判断 e = 1,其实
if(e == 1) {
System.arraycopy(data, 12 + 2 + naluStartCodeIndex, h264Buffer, frameSize, size - 12 - 2 - naluStartCodeIndex);
frameSize += (size - 12 - 2 - naluStartCodeIndex);
//拷贝一帧完整的h264 码流数据回调出去
byte[] frameBuffer = new byte[frameSize];
System.arraycopy(h264Buffer,0,frameBuffer,0,frameSize);
frameSize = 0;
hasAddNaluHead = false;
if (onDumpListener != null) {
onDumpListener.onVideo(frameBuffer, frameType, sequenceNumber, timeStamp);
}
} else {
LogUtils.e("分片包的最后一帧 e 标志位异常 e= :" + e);
}
} else {
byte[] h264 = new byte[size - 12 - naluStartCodeIndex];
System.arraycopy(data, 12 + naluStartCodeIndex, h264, 0, size - 12 - naluStartCodeIndex);
if (onDumpListener != null) {
onDumpListener.onVideo(h264, naluType, sequenceNumber, timeStamp);
}
}
} else {
int naluStartCodeIndex = 0;
if(data[14] == 0x00 && data[15] == 0x00 && data[16] == 0x01) {
naluStartCodeIndex = 3;
}
if(data[14] == 0x00 && data[15] == 0x00 && data[16] == 0x00 && data[17] == 0x01) {
naluStartCodeIndex = 4;
}
//进入分片模式 [RTP head 12byte] [FU indicator 1byte] [FU head 1byte] [data]
/**
* 读取1字节
* FU-A indicator type
* +---------------+
* |0|1|2|3|4|5|6|7|
* +-+-+-+-+-+-+-+-+
* |F|NRI| Type |
* +---------------+
* h264 码流时:
* F (1bit) ---H264 F 全称forbidden_zero_bit,禁止位
* 作用:当网路发现nal单元有比特错误时,可以设置为1,以便接收方丢掉改单元否则默认为0
* NRI 2bit) 对应 h264 NRI 全称nal_ref_idc,重要性指示位,
* 作用:标志改NAL单元用于重建时的重要性,
* 值越大越重要,取值范围00~11
* type:FU-A 为 28
*/
//FU-A indicator 的后5位为 分片类型,此处只处理FU-A = 28 的类型
int fuAType = data[12] & 0x1f;
if(fuAType == FRAGMENT_UNIT_A_TYPE) {
/**
* +---------------+
* |0|1|2|3|4|5|6|7|
* +-+-+-+-+-+-+-+-+
* |S|E|R| Type |
* +---------------+
*
* H264 各字段含义:
* S,为1表示分片的开始;
* E,为1表示分片的结束,否则为0;
* R,保留位;
* Type就是NALU头中的Type,取1-23值。
*/
int s = (data[13] & 0x80) >> 7;
int e = (data[13] & 0x40) >> 6;
int naluType = data[13] & 0x1f;
//nalu head = FU-A indicator 前3位 的F NRI + FU head 后5位 Type
byte naluHead = (byte)((data[12] & 0xe0 ) | (data[13] & 0x1f));
LogUtils.v("175---------s :" + s +" e:" + e +" nalueType:" + naluType +" size:" + size + " fu_type :" + ByteUtil.bytesToHexString(new byte[]{data[13]}));
//分片开始
if(e == 0) {
//再拷贝数据,剔除起始码数据
System.arraycopy(data, 12 + 2 + naluStartCodeIndex, h264Buffer, frameSize, size - 12 - 2 - naluStartCodeIndex);
frameSize += (size - 12 - 2 - naluStartCodeIndex);
} else {
LogUtils.e("分片包的开始包 e 标志位异常 e= :" + e);
}
}
}
} else if (payLoadType == AUDIO_TYPE) {
// 其中RTP载荷的一个字节为0x00,第二个字节为0x10。
// 第三个字节和第四个字节保存AAC Data的大小,最多只能保存13bit,
// 第三个字节保存数据大小的高八位,
// 第四个字节的高5位保存数据大小的低5位。
// 注意 取出的 aac 为不带adts 头的数据
int aacLength = ((data[14] & 0xFF) << 5 data15 0xf8>> 3);
LogUtils.v("收到音频帧长度为: "+aacLength +" size :" + size);
if(aacLength == (size - 16)) {
byte[] aacRaw = new byte[aacLength];
System.arraycopy(data,16,aacRaw,0,aacLength);
//此处也可以不添加adts 头,当不添加adts 头时,需要配置mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 0);
byte[] aacFrame = addAdtsHeader(aacRaw);
if(onDumpListener != null) {
onDumpListener.onAudio(aacFrame,sequenceNumber,timeStamp);
}
} else {
LogUtils.v("音频数据非法,请检查发送端的数据报文");
}
}
return true;
} else {
LogUtils.e("待解析的rtp 数据异常,停止解析");
return false;
}
}
2.2.4 C++ 版本组包:
#include
#define RTP_VESION 2
#define RTP_PAYLOAD_TYPE_H264 96
#define RTP_PAYLOAD_TYPE_AAC 97
#define RTP_HEADER_SIZE 12
#define RTP_MAX_PKT_SIZE 1400
#define FRAGMENT_UNIT_A_TYPE 28
/*
*
* 0 1 2 3
* 7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P|X| CC |M| PT | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | synchronization source (SSRC) identifier |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | contributing source (CSRC) identifiers |
* : .... :
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 其中的:n是一种位表示法,这个结构体跟RTP的头部一一对应
*/
struct RtpHeader
{
/* byte 0 */
uint8_t csrcLen:4;
uint8_t extension:1;
uint8_t padding:1;
uint8_t version:2;
/* byte 1 */
uint8_t payloadType:7;
uint8_t marker:1;
/* bytes 2,3 */
uint16_t seq;
/* bytes 4-7 */
uint32_t timestamp;
/* bytes 8-11 */
uint32_t ssrc;
};
struct RtpPacket
{
struct RtpHeader rtpHeader;
uint8_t payload[0];
};
class rtp {
public:
rtp();
~rtp();
void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
uint16_t seq, uint32_t timestamp, uint32_t ssrc);
int rtpSendPacket(int socket, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);
};
源文件:
组包源文件:
rtp::rtp() {
}
rtp::~rtp() {
}
void rtp::rtpHeaderInit(struct RtpPacket *rtpPacket, uint8_t csrcLen, uint8_t extension, uint8_t padding, uint8_t version,
uint8_t payloadType, uint8_t marker, uint16_t seq, uint32_t timestamp, uint32_t ssrc) {
rtpPacket->rtpHeader.csrcLen = csrcLen;
rtpPacket->rtpHeader.extension = extension;
rtpPacket->rtpHeader.padding = padding;
rtpPacket->rtpHeader.version = version;
rtpPacket->rtpHeader.payloadType = payloadType;
rtpPacket->rtpHeader.marker = marker;
rtpPacket->rtpHeader.seq = seq;
rtpPacket->rtpHeader.timestamp = timestamp;
rtpPacket->rtpHeader.ssrc = ssrc;
}
int rtp::rtpSendPacket(int socket, char *ip, int16_t port, struct RtpPacket *rtpPacket, uint32_t dataSize) {
struct sockaddr_in addr;
int ret;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);
rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);
rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);
ret = sendto(socket, (void*)rtpPacket, dataSize+RTP_HEADER_SIZE, 0,
(struct sockaddr*)&addr, sizeof(addr));
rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);
rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);
rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);
return ret;
}
收包:
void dumpHead(const char *data, int32_t size);
/**
* 从数据data 中解析h264裸流数据
* @param data rtp 数据报文
* @param size rtp 数据报文大小
* @param seq rtp 数据报文序列号,非常重要判断丢帧的重要标志
* @param timestamp rtp 数据报文时间戳 作同步用
* @param mark rtp 数据报文 头部mark字段1 表示一帧结束,0表示未结束,通常用于分片中
*/
void dumpH264(const char *data, int32_t size, uint16_t seq, uint32_t timestamp,uint32_t mark);
/**
* 从数据data 中解析aac裸流数据
* @param data rtp 数据报文
* @param size rtp 数据报文大小
* @param seq rtp 数据报文序列号,非常重要判断丢帧的重要标志
* @param timestamp rtp 数据报文时间戳 作同步用
*/
void dumpAAC(const char *data, int32_t size, uint16_t seq, uint32_t timestamp);
void RtpDemux::dumpHead(const char *data, int32_t size) {
if (data && size > 0) {
RtpHeader header = {0};
//第1字节
header.version = data[0] & 0xc0;
header.padding = data[0] & 0x20;
header.extension = data[0] & 0x10;
header.csrcLen = data[0] & 0x0f;
//第2字节 m_pt
header.marker = (data[1] & 0x80) >> 7;
header.payloadType = data[1] & 0x7f;
//2-3字节 序列号
header.seq = (data[2] << 8 & 0xff00) | (data[3] & 0x00ff);
//4-7字节序列号
header.timestamp = (data[4] << 24 & 0xff000000) | (data[5] << 16 & 0x00ff0000) | (data[6] << 8 & 0x0000ff00)
| (data[7] & 0x000000ff);
//8-11字节同步信号源
header.ssrc = (data[8] << 24) | (data[9] << 16) | (data[10] << 8 data11 logi11-------rtp payloadtype='%d,marker' d timestamp:d size : d header.payloadtype header.marker header.seq header.timestamp size if header.payloadtype='= RTP_PAYLOAD_TYPE_AAC)' dumpaacdata 12 size - 12 header.seq header.timestamp else if header.payloadtype='= RTP_PAYLOAD_TYPE_H264)' dumph264data size header.seq header.timestamp header.marker else loge rtp h264 param data 12 rtp h264 param size param seq param timestamp param mark void rtpdemux::dumph264const char data int32_t size uint16_t seq uint32_t timestamp uint32_t mark if data size> 0) {
if (size <= rtp_max_pkt_size rtp_header_size rtp header12bytes nalu header 1byte nalu payload int nalustartcodeindex='0;' if data12='= 0x00' data13='= 0x00' data14='= 0x01)' nalustartcodeindex='3;' if data12='= 0x00' data13='= 0x00' data14='= 0x00' data15='= 0x01)' nalustartcodeindex='4;' int nalutype='data[12' nalustartcodeindex 0x1f logi141--------00 00 01 : nalutype:d mark :d nalustartcodeindex='%d' rtp size='%d' seq='%d",' nalutype marknalustartcodeindexsizeseq if nalutype='= FRAGMENT_UNIT_A_TYPE)' int s='(data[13]' 0x80>> 7;
int e = (data[13] & 0x40) >> 6;
int frameType = data[13] & 0x1f;
LOGE("141-----------分片包的最后一组数据: s = %d e = %d , mark = %d, "
"frameType = %d data[13] = %x rtp size = %d seq = %d fu-indicator = %x fu-head = %x",
s, e, mark,
frameType, data[13],size,seq,data[12],data[13]);
//分片结束,可以判断 e = 1,其实 也可以判断 mark == 1 && e == 1 这样更严谨一点
if (e == 1) {
memcpy(m_h264Buf + m_nHasUnitFrameSize, data + 12 + 2 + naluStartCodeIndex,
size - 12 - 2 - naluStartCodeIndex);
m_nHasUnitFrameSize += (size - 12 - 2 - naluStartCodeIndex);
//拷贝一帧完整的h264 码流数据回调出去
char *frameBuffer = new char[m_nHasUnitFrameSize];
memset(frameBuffer, 0, m_nHasUnitFrameSize);
memcpy(frameBuffer, m_h264Buf, m_nHasUnitFrameSize);
//int frameSize = m_nHasUnitFrameSize;
if (this->m_callJava) {
this->m_callJava->onVideo(THREAD_CHILD, frameType,
reinterpret_cast(frameBuffer),
m_nHasUnitFrameSize, timestamp, frameType == H264_IDR);
}
m_nHasUnitFrameSize = 0;
memset(m_h264Buf, 0, H264_MTU);
delete[] frameBuffer;
} else {
LOGE("141-----分片包的最后一帧 e 标志位异常 e= :%d", e);
}
} else {
char *h264 = new char[size - 12 - naluStartCodeIndex];
memset(h264, 0, size - 12 - naluStartCodeIndex);
memcpy(h264, data + 12 + naluStartCodeIndex, size - 12 - naluStartCodeIndex);
//回调的数据不带起始码
if (this->m_callJava) {
this->m_callJava->onVideo(THREAD_CHILD, naluType, reinterpret_cast(h264),
size - 12 - naluStartCodeIndex, timestamp, naluType == H264_IDR);
}
delete [] h264;
}
} else {
//进入分片模式 [RTP head 12byte] [FU indicator 1byte] [FU head 1byte] [data]
int naluStartCodeIndex = 0;
if (data[14] == 0x00 && data[15] == 0x00 && data[16] == 0x01) {
naluStartCodeIndex = 3;
}
if (data[14] == 0x00 && data[15] == 0x00 && data[16] == 0x00 && data[17] == 0x01) {
naluStartCodeIndex = 4;
}
/**
* 读取1字节
* FU-A indicator type
* +---------------+
* |0|1|2|3|4|5|6|7|
* +-+-+-+-+-+-+-+-+
* |F|NRI| Type |
* +---------------+
* h264 码流时:
* F (1bit) ---H264 F 全称forbidden_zero_bit,禁止位
* 作用:当网路发现nal单元有比特错误时,可以设置为1,以便接收方丢掉改单元否则默认为0
* NRI 2bit) 对应 h264 NRI 全称nal_ref_idc,重要性指示位,
* 作用:标志改NAL单元用于重建时的重要性,
* 值越大越重要,取值范围00~11
* type:FU-A 为 28
*/
//FU-A indicator 的后5位为 分片类型,此处只处理FU-A = 28 的类型
int fuAType = data[12] & 0x1f;
if (fuAType == FRAGMENT_UNIT_A_TYPE) {
/**
* +---------------+
* |0|1|2|3|4|5|6|7|
* +-+-+-+-+-+-+-+-+
* |S|E|R| Type |
* +---------------+
*
* H264 各字段含义:
* S,为1表示分片的开始;
* E,为1表示分片的结束,否则为0;
* R,保留位;
* Type就是NALU头中的Type,取1-23值。
*/
int s = (data[13] & 0x80) >> 7;
int e = (data[13] & 0x40) >> 6;
int naluType = data[13] & 0x1f;
//nalu head = FU-A indicator 前3位 的F NRI + FU head 后5位 Type
char naluHead = ((data[12] & 0xe0) | (data[13] & 0x1f));
LOGI("175---------收到分片包 s : %d e : %d seq = %d, naluType : %d size : %d", s, e, seq,naluType, size);
//分片开始
if (e == 0) {
//剔除起始码数据,再拷贝数据
memcpy(m_h264Buf + m_nHasUnitFrameSize, data + 12 + 2 + naluStartCodeIndex,
size - 12 - 2 - naluStartCodeIndex);
m_nHasUnitFrameSize += size - 12 - 2 - naluStartCodeIndex;
} else {
LOGE("分片包的开始包 e 标志位异常 e= :%d", e);
}
}
}
}
}
void RtpDemux::dumpAAC(const char *data, int32_t size, uint16_t seq, uint32_t timestamp) {
if (data && size > 0) {
int aacLength = ((data[2] & 0xFF) << 5 data3 0xf8>> 3);
//LOGI("133----收到音频包数据大小为:%d", aacLength);
if (aacLength == (size - 4)) {
char *aacRaw = new char[aacLength];
memset(aacRaw, 0, aacLength);
memcpy(aacRaw, data + 4, aacLength);
char *adtsFrameBuffer = addADTSAAC(aacRaw, aacLength);
if (this->m_callJava) {
this->m_callJava->onAudio(THREAD_CHILD, AUDIO_AAC_ADTS_FRAME,
reinterpret_cast(adtsFrameBuffer), aacLength + 7,
timestamp);
}
delete[] aacRaw;
} else {
LOGE("141---音频数据非法,请检查发送端的数据报文");
}
} else {
LOGI("音频数据非法,请检查发送端的数据报文");
}
}
三、RTP 协议优势
- 对于UDP 传输,提供了包乱序解决方案
- 解决方案:可以根据RTP包的序列号来排序。
- 提供了时间戳与包序列号两种机制来确定发包顺序,当出现包丢失时可根据丢失的包进行重发而不必重发整个报文。
- 提供解决音视频同步的方案
- 根据声音流和图像流的相对时间(即RTP包的时间戳),以及它们的绝对时间(即对应的RTCP包中的RTCP),可以实现声音和图像的同步。