win7invalidpar (win7invalidpartitiontable开不了机)

0.引言

为了更好理解本篇文章,可以先阅读前面几篇文章,文章列表如下:

详解RTP打包AAC实战分析(1)

详解RTP协议之H264封包和解包实战

详解RTP协议之H264封包细节(1)

详细解析RTSP框架和数据包分析(1)

手把手搭建RTSP流媒体服务器

RTP协议

HLS实战之Wireshark抓包分析

HTTP实战之Wireshark抓包分析

建议:阅读本文前,一定要阅读前面的文章,只有理解了原理,才能够真正读懂代码。


1.Rtp封包AAC重要步骤

把AAC数据封装成RTP包,有以下一些重要步骤,如下:

(1)读入AAC文件时,将AAC的前7个字节(或9个字节)的ADTS 电脑header去掉,跳过adts header

(2)添加RTP Header。

(3)添加2字节的AU_HEADER_LENGTH,一般是2个字节,也有可能大于2个字节,根据实际情况,灵活改变。

(4)添加2字节的AU_HEADER,一般是2个字节,也有可能大于2个字节,根据实际情况,灵活改变。

(5)从第17字节开始就是payload(去掉ADTS Header的aac数据)封装数据了。


1.1 Rtp封包AAC格式

RTP承载aac的格式,由如下组成:

(1)2个字节的AU-headers-length。

(2)n个AU-header,每个2字节。

(3)n个AU,是aac去掉adts的payload。

存储格式如下:

注意:前面的RTP header+AU_HEADER_LENGTH一般是固定结构,AU_HEADER的个数和AU是一一对应。AU_HEADER_LENGTH是描述所有AU_HEADER的总bit数量,一般是被16整除,这个大小在SDP文件中会有描述,一般一个AU_HEADER也有16bit

发送数据时,推流协议可以自己控制,可以不支持那么复杂的协议,rtp aac每次只发送一个AU,也就只有一个AU_HEADER

一个packet,一般只含1个AU。也有包含多个AU的情况,如下图。但实际工程项目中,打包的情况很少,根据实际情况来设计。


1.2 重要数据结构说明

(1)AU_HEADER_LENGTH

AU_HEADER_LENGTH表示所有au-header的长度,单位是Bit,一般是2个字节。一个au-header的长度是2个字节,因为可以有多个au-header,所以AU-Headers-Length的值是16的倍数。一般音频都是单个音频数据流的发送,所以AU_HEADER_LENGTH的值是16bit,2个字节。

//AU_HEADER_LENGTH

bytes[12] = 0x00; //?位

bytes[13] = 0x10; //低位 只有?个AU_HEADER

因为单位是bit, 除以8就是auHeader的字节?度;?因为单个auheader字节?度2字节,再除以2就是auheader的个数,所以只有1个。

(2)AU-header

au-header的?13个bits就是?个au 的字节?度,如下所示:

//AU_HEADER

bytes[14] = (byte)((len & 0x1fe0) >> 5); //?位

bytes[15] = (byte)((len & 0x1f) << 3); //低位

AU-Header数据段的格式如下图:



1.3 实际的音频数据用AU表示

(1)当RTSP的?频使?AAC格式时, SDP文件的内容如下:

v=0 o=- 16128587303007558182 16128587303007558182 IN IP4 WINDOWS-75ID U9Q s=Unnamed i=N/A 5 c=IN IP4 0.0.0.0 t=0 0 a=tool:vlc 3.0.5 a=recvonly a=type:broadcast a=charset:UTF-8 a=control:rtsp://192.168.129.15:8554/ m=audio 0 RTP/AVP 96 b=AS:128 b=RR:0 a=rtpmap:96 mpeg4-generic/22050 a=fmtp:96 streamtype=5; profile-level-id=15; mode=AAC-hbr; config =138856e500; sizeLength=13; indexLength=3; indexDeltaLength=3; Pr ofile=1; a=control:rtsp://192.168.2.195:8554/trackID=4 m=video 0 RTP/AVP 96 b=AS:800 b=RR:0 a=rtpmap:96 H264/90000 a=fmtp:96 packetization-mode=1;profile-level-id=42c01e;sprop-para meter-sets=Z0LAHtoCQKeX/8CgAJ/EAAADAZAAAF2qPFi6gA==,aM43IA==; a=control:rtsp://192.168.129.15:8554/trackID=5

(2)上面这些参数是符合RFC规范,具体链接如下:

https://tools.ietf.org/html/rfc3640

具体界面如下:

(3)根据上面参数,重点说明config, SizeLength, IndexLength, IndexDeltaLength的作?。

前两个字节的为ios-14996-3中定义的AudioSpecificConfig, 前13个bits的格式如下表:


samplingFrequencyIndex的取值,根据不同的索引,匹配不同的采样值。如下图:

根据上面的图给出的格式,这里就以1388举例说明,1388 转换成2进制为 0001 0011 1000 1000。

audioObjectType为 00010 , 即 2,samplingFrequencyIndex为 0111 , 即 7 , 对应的采样频率为 22050,channelConfiguration为 0001 , 表示channel数量为1。

上面的sizeLength=13; indexLength=3; indexDeltaLength=3涉及到?频的AU Header.AU Header解决了?个RTP包容纳多个?频包的问题。

每个AU-Header包含以下信息:

(4)描述了当前的RTP包含了多少个?频包,也就是AU的个数。

(5)每个?频包(AU)的??是多少。

(6)时间戳是多少。

AU-size、AU-Index(描述一个RTP包的AU序号)、AU-Index-delta具体含义描述如下图:

注意:其它的值都是可选的, 如果sdp中没有出现相关的参数(或者为0), 则表示它们不会出现。以最简单的情况举例, 假设aac数据?度为200字节, 只有?个au-header。200 的?进制为 0000011001000 (补?为13 bits)。AU-headers-length 值为16, 因为只有?个au-header, au-header中只有AU-size和AU-Index, 共占?16bits。整个au-header数据段的内容如下:


通常情况下, ?个rtp中只有?个aac包, 不需要加再AU-Header, 那么sdp中的aac参数可以简化为如下图:



2.源码解析

2.1 SDP文件和封装细节说明

(1)修改了indexLength的长度(相当于去掉了indexLength=3)。源码修改成如下:

创建sdp时,一定要按照格式来,字符千万不能写错了,比如写错audio,否则就有可能写错。

/*可以参考ffmpeg sdp.cSDP中几个参数含义:config,就是AudioSpecificConfig的十六进制表示*/void aac_rtp_create_sdp(uint8_t *file, uint8_t *ip, uint16_t port, uint16_t profile, uint16_t chn, uint16_t freq, uint16_t type){ char buff[1024] = {0}; char typeName[64] = {0}; char demo[] = "m=audio 9832 RTP/AVP 97\n" "a=rtpmap:97 mpeg4-generic/44100/2\n" "a=fmtp:97 sizeLength=13;mode=AAC-hbr;config=1210;\n" "c=IN IP4 127.0.0.1"; char demo[] = "m=audio %d RTP/AVP %d\n" "a=rtpmap:%d %s/%d/%d\n" "a=fmtp:%d streamtype=5;profile-level-id=1;sizeLength=13;IndexLength=3;indexDeltaLength=3;mode=AAC-hbr;config=%d;\n" "c=IN IP4 %s"; /*char demo[] = "m=audio %d RTP/AVP %d\n" "a=rtpmap:%d %s/%d/%d\n" "a=fmtp:%d streamtype=5;profile-level-id=1;sizeLength=13;indexDeltaLength=3;mode=AAC-hbr;config=%d;\n" "c=IN IP4 %s";*/ uint16_t config = 1410, _freq = 8; int fd; ........ // 从aac adts header读取出来的profile是被减1的 //这些参数就是很关键的,adts header虽然很跳过了,但是信息还是很关键,关键信息要保存 config = profile + 1; config <<= 5; // 5aac的profile, 通常情况是1, 或者2 config |= _freq; config <<= 4; //aac的采样频率的索引 config |= chn; config <<= 3; //转成16进制 config = ((config>>12)&0xF)*1000 + ((config>>8)&0xF)*100 + ((config>>4)&0xF)*10 + ((config>>0)&0xF); snprintf(buff, sizeof(buff), demo, port, type, type, typeName, freq, chn, type, config, ip); remove(file); if((fd = fopen(file, "wt")) > 0) { //把buff的数据写到创建的aac.sdp文件里面去 fwrite(buff, strlen(buff), 1, fd); fclose(fd); }


(2)这个时候再去拉取流播放,就会报错。

电脑

(3)在ffmpeg的源码文件rtpdec_mpeg4.c,对应源码如下:

下面推荐的这两个源码文件,是非常值得学习,就是rtpdec_mpeg4.c和rtpenc_aac.c。

static int rtp_parse_mp4_au(PayloadContext *data, const uint8_t *buf, int len){ int au_headers_length, au_header_size, i; GetBitContext getbitcontext; if (len < 2) return AVERROR_INVALIDDATA; /* decode the first 2 bytes where the AUHeader sections are stored length in bits */ au_headers_length = AV_RB16(buf); if (au_headers_length > RTP_MAX_PACKET_LENGTH) return -1; data->au_headers_length_bytes = (au_headers_length + 7) / 8; /* skip AU headers length section (2 bytes) */ buf += 2; len -= 2; if (len < data->au_headers_length_bytes) return AVERROR_INVALIDDATA; init_get_bits(&getbitcontext, buf, data->au_headers_length_bytes * 8); /* XXX: Wrong if optional additional sections are present (cts, dts etc...) */ //把AU-size和AU-Index/AU-Index-delta加起来,如果不这样做,就会报上述问题 au_header_size = data->sizelength + data->indexlength; if (au_header_size <= 0 || (au_headers_length % au_header_size != 0)) return -1; data->nb_au_headers = au_headers_length / au_header_size; if (!data->au_headers || data->au_headers_allocated < data->nb_au_headers) { av_free(data->au_headers); data->au_headers = av_malloc(sizeof(struct AUHeaders) * data->nb_au_headers); if (!data->au_headers) return AVERROR(ENOMEM); data->au_headers_allocated = data->nb_au_headers; } for (i = 0; i < data->nb_au_headers; ++i) { data->au_headers[i].size = get_bits_long(&getbitcontext, data->sizelength); data->au_headers[i].index = get_bits_long(&getbitcontext, data->indexlength); } return 0;}

rtpenc_aac.c的函数ff_rtp_send_aac(xxx)的源码如下:

void ff_rtp_send_aac(AVFormatContext *s1, const uint8_t *buff, int size){ RTPMuxContext *s = s1->priv_data; AVStream *st = s1->streams[0]; const int max_au_headers_size = 2 + 2 * s->max_frames_per_packet; int len, max_packet_size = s->max_payload_size - max_au_headers_size; uint8_t *p; /* skip ADTS header, if present */ if ((s1->streams[0]->codecpar->extradata_size) == 0) { size -= 7; buff += 7; } /* test if the packet must be sent */ len = (s->buf_ptr - s->buf); if (s->num_frames && (s->num_frames == s->max_frames_per_packet || (len + size) > s->max_payload_size || av_compare_ts(s->cur_timestamp - s->timestamp, st->time_base, s1->max_delay, AV_TIME_BASE_Q) >= 0)) { int au_size = s->num_frames * 2; p = s->buf + max_au_headers_size - au_size - 2; if (p != s->buf) { memmove(p + 2, s->buf + 2, au_size); } /* Write the AU header size */ AV_WB16(p, au_size * 8); ff_rtp_send_data(s1, p, s->buf_ptr - p, 1); s->num_frames = 0; } if (s->num_frames == 0) { s->buf_ptr = s->buf + max_au_headers_size; s->timestamp = s->cur_timestamp; } if (size <= max_packet_size) { p = s->buf + s->num_frames++ * 2 + 2; AV_WB16(p, size * 8); memcpy(s->buf_ptr, buff, size); s->buf_ptr += size; } else { int au_size = size; max_packet_size = s->max_payload_size - 4; p = s->buf; AV_WB16(p, 2 * 8); while (size > 0) { len = FFMIN(size, max_packet_size); AV_WB16(&p[2], au_size * 8); memcpy(p + 4, buff, len); ff_rtp_send_data(s1, p, len + 4, len == size); size -= len; buff += len; } }}

调用这个函数rtp_parse_mp4_au(xxx),出现了异常。源码如下:

static int rtp_parse_mp4_au(PayloadContext *data, const uint8_t *buf, int len){ int au_headers_length, au_header_size, i; GetBitContext getbitcontext; if (len < 2) return AVERROR_INVALIDDATA; /* decode the first 2 bytes where the AUHeader sections are stored length in bits */ au_headers_length = AV_RB16(buf); if (au_headers_length > RTP_MAX_PACKET_LENGTH) return -1; data->au_headers_length_bytes = (au_headers_length + 7) / 8; /* skip AU headers length section (2 bytes) */ buf += 2; len -= 2; if (len < data->au_headers_length_bytes) return AVERROR_INVALIDDATA; init_get_bits(&getbitcontext, buf, data->au_headers_length_bytes * 8); /* XXX: Wrong if optional additional sections are present (cts, dts etc...) */ //把AU-size和AU-Index/AU-Index-delta加起来,如果不这样做,就会报上述问题 au_header_size = data->sizelength + data->indexlength; if (au_header_size <= 0 || (au_headers_length % au_header_size != 0)) return -1; data->nb_au_headers = au_headers_length / au_header_size; if (!data->au_headers || data->au_headers_allocated < data->nb_au_headers) { av_free(data->au_headers); data->au_headers = av_malloc(sizeof(struct AUHeaders) * data->nb_au_headers); if (!data->au_headers) return AVERROR(ENOMEM); data->au_headers_allocated = data->nb_au_headers; } for (i = 0; i < data->nb_au_headers; ++i) { data->au_headers[i].size = get_bits_long(&getbitcontext, data->sizelength); data->au_headers[i].index = get_bits_long(&getbitcontext, data->indexlength); } return 0;}


(4)AU_HEADER_LENGTH描述所有AU_HEADER的总bit数,一般是需要16整除,如果不够整除是可以用padding值来去补充。AU_HEADER的大小是需要SDP去描述。一般一个AU_HEADER大小都是16bit。

注意:目前源码中cts、dts暂时是不支持。

只有一个AU_HEADER才写成10。AU_HEADER再到AU。

AAC帧可能要拆分成多个包,如果AAC帧的size超过了RTP打包的size,那就需要分包。一般情况下AAC帧是不会超过RTP打包的size,即不用分包。

(5)我这里就是一个包对应一个AU的情况,暂时不考虑一个包对应多个AU。发送数据时,推流协议是可以自己控制,可以不支持那么复杂的协议。rtp aac可以每次只发送一个AU,AU_HEADER用AU-headers-length占2个字节,高13bit是 AU size。如果一次发送完,代码下的bytes就会等于0。打包把RTP_FIXED_HEADER + N_AU_HEADER + packer->pkt.payloadlen会全部打包起来序列化(就是把数据按照RTP协议的格式封装到RTP header里面去),然后发送。如果是最后一个packet,这里的AU mark的标记就会设置为1。N_AU_HEADER为什么是4?就是AU_HEADER_LENGTH +AU_HEADER的大小,就表示是4个字节。

前面已经把AU header序列化到RTP header里面去了,接下来就是封装payload了,封装的大小n + N_AU_HEADER() + packer->pkt.payloadlen,其中n表示RTP头长度,N_AU_HEADER表示整个AU header长度,即AU_HEADER_LENGTH +AU_HEADER的大小,packer->pkt.payloadlen表示payload长度。

前面已经都封装好了,这样就回调用户的回调函数packer->handler.packet(xxx),进行rtp包的数据发送。源码如下:

/** * @brief rtp_mpeg4_generic_pack_input * @param pack * @param data 包括adts header * @param bytes 包括adts header的长度 * @param timestamp * @return <0:失败 */static int rtp_mpeg4_generic_pack_input(void* pack, const void* data, int bytes, uint32_t timestamp){ int r; int n, size; uint8_t *rtp; uint8_t header[4]; const uint8_t *ptr; struct rtp_encode_mpeg4_generic_t *packer; packer = (struct rtp_encode_mpeg4_generic_t *)pack; packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); r = 0; ptr = (const uint8_t *)data; if (0xFF == ptr[0] && 0xF0 == (ptr[1] & 0xF0) && bytes > 7) { // skip ADTS header 跳过adts头部 assert(bytes == (((ptr[3] & 0x03) << 11) | (ptr[4] << 3) | ((ptr[5] >> 5) & 0x07))); ptr += 7; // 这里直接判断为7个字节 bytes -= 7; } // 担心AAC帧可能要拆分多个RTP包 for (size = bytes; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { // 3.3.6. High Bit-rate AAC // SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength = 3; // au-header的高13个bits就是一个au 的字节长度: header[0] = 0x00; // 高位 header[1] = 0x10; // 低位 16-bits AU headers-lenght // AU headers 用2个字节,高13bit是 AU size header[2] = 0; header[3] = 0; header[2] = (uint8_t)(size >> 5); // 高位 header[3] = (uint8_t)(size & 0x1f) << 3; // 低位 这里一个包只发送一个AU packer->pkt.payload = ptr; // N_AU_HEADER 占据了4个字节,AU-headers-length占2个字节,AU-header占2个字节 packer->pkt.payloadlen = (bytes + N_AU_HEADER + RTP_FIXED_HEADER) <= packer->size ? // 判断是否要划分多个AU bytes : (packer->size - N_AU_HEADER - RTP_FIXED_HEADER); ptr += packer->pkt.payloadlen; bytes -= packer->pkt.payloadlen; // 剩余的字节数 n = RTP_FIXED_HEADER + N_AU_HEADER + packer->pkt.payloadlen; rtp = (uint8_t*)packer->handler.alloc(packer->cbparam, n); // 分配缓存,以备用来保存序列化数据 if (!rtp) return -ENOMEM; // Marker (M) bit: The M bit is set to 1 to indicate that the RTP packet // payload contains either the final fragment of a fragmented Access // Unit or one or more complete Access Units packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; // 最后一个AU mark设置为1,否则为0 n = rtp_packet_serialize_header(&packer->pkt, rtp, n); if (n != RTP_FIXED_HEADER) { assert(0); return -1; } memcpy(rtp + n, header, N_AU_HEADER); // N_AU_HEADER为什么是4, AU_HEADER_LENGTH AU_HEADER memcpy(rtp + n + N_AU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); // 封装payload r = packer->handler.packet(packer->cbparam, rtp, n + N_AU_HEADER + packer->pkt.payloadlen, packer->pkt.rtp.timestamp, 0); packer->handler.free(packer->cbparam, rtp); } return r;}


2.2 解封装源码细节

(1)首先通过反序列化,把RTP头部解析出来。

(2)然后,再解析AU。

(3)解析出RTP头部后,就可以知道payload的起始位置。注意,这里的payload是包括了AU_header_length +au_header占用的字节了。就可以求出 au_header的大小(单位字节数),但是暂时还不包括AU-header-length的大小。一般AU-header-length是固定占用2个字节。

注意:大部分情况下,SDP都是这类参数,SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength=3;一般au header占用2字节,其他格式不支持,如CTS和DTS,如果加上au header就会超过2个字节,这里暂时不支持。

(4)有了AU-header-length就可以算出au 的个数。那ptr就会跳过au header(大小就是AU-header-length=2)的起始位置,pau指向au的起始位置。注意这里的au_header_length就是所有AU_Header的总长度。如下图所示:


(5)由于前面已经获取了AU size,即找到了AU的位置,这样就可以去解析rtp_payload部分了。

(6)缓存完各个au,ptr就可以跳过au_header的size,pau就跳过au的size,最后就到了真正的数据。

(7)收到真正完整的数据,就可以返回给应用层去处理了。

上面所述步骤的源码如下:

static int rtp_decode_mpeg4_generic(void* p, const void* packet, int bytes){ int i, size; int au_size; int au_numbers; int au_header_length; const uint8_t *ptr, *pau, *pend; struct rtp_packet_t pkt; struct rtp_payload_helper_t *helper; helper = (struct rtp_payload_helper_t *)p; //1. 反序列化,把RTP 头解析出来 if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4) return -EINVAL; rtp_payload_check(helper, &pkt); // save payload ptr = (const uint8_t *)pkt.payload; // 这里还是包括了AU_header_length au_header占用的字节的 pend = ptr + pkt.payloadlen; // AU-headers-length AU-headers-length是固定占用2个字节 au_header_length = (ptr[0] << 8) + ptr[1]; // 读取长度 au_header_length = (au_header_length + 7) / 8; // bit -> byte 计算出来au_heade占用的字节情况 if (ptr + au_header_length /*AU-size*/ > pend || au_header_length < 2) //如果au_header_length至少都是2个字节,如果小于2个字节,就不对了.还有一种情况就是越界了 { assert(0); //helper->size = 0; helper->lost = 1; //helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; return -1; // invalid packet } // 3.3.6. High Bit-rate AAC // SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength=3; 一般au header占用2字节,其他格式不支持 au_size = 2; // only AU-size au_numbers = au_header_length / au_size; // 计算au个数 assert(0 == au_header_length % au_size); ptr += 2; // skip AU headers length section 2-bytes ; 此时ptr指向au header的起始位置 pau = ptr + au_header_length; // point to Access Unit 跳过AU header才是真正的数据; 此时pau指向的是au的起始位置 for (i = 0; i < au_numbers; i++) { size = (ptr[0] << 8) | (ptr[1] & 0xF8); // 获取au的size,对应函数rtp_mpeg4_generic_pack_input(xxx),前面13位表示au size size = size >> 3; // 右移3bit 转成字节 if (pau + size > pend) { assert(0); //helper->size = 0; helper->lost = 1; //helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; return -1; // invalid packet } // TODO: add ADTS/ASC ??? pkt.payload = pau; pkt.payloadlen = size; rtp_payload_write(helper, &pkt); ptr += au_size; // 跳过au header size pau += size; // 跳过au size if (au_numbers > 1 || pkt.rtp.m) // 收到完整数据再发送给应用层 { rtp_payload_onframe(helper); } } return helper->lost ? 0 : 1; // packet handled}


(8)rtp_payload_helper_t* helper就是作为缓存,就是把多个AU 组成一帧数据。这里记录了缓存的字节数,并进行累加,只要不超过缓存的容量。如果超过了超过缓存的容量,就需要重新分配内存,如果没超过,就直接拷贝到helper指向的缓存里面去,即helper->ptr + helper->size指向的位置。源码如下:

/** * @brief rtp_payload_write * @param helper 缓存每个au解析出来的数据 * @param pkt * @return */int rtp_payload_write(struct rtp_payload_helper_t* helper, const struct rtp_packet_t* pkt){ int size; size = helper->size + pkt->payloadlen;//记录缓存字节数 if (size > helper->maxsize || size < 0) return -EINVAL; if (size > helper->capacity)//最大缓存 { void *ptr; size += size / 4 > 16000 ? size / 4 : 16000; ptr = realloc(helper->ptr, size); if (!ptr) { //helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; helper->lost = 1; //helper->size = 0; return -ENOMEM; } helper->ptr = (uint8_t*)ptr; helper->capacity = size; } assert(helper->capacity >= helper->size + pkt->payloadlen); memcpy(helper->ptr + helper->size, pkt->payload, pkt->payloadlen); helper->size += pkt->payloadlen; return 0;}


(9)前面rtp_payload_helper_t* helper就是作为缓存一帧帧完整的数据,然后调用用户设置的回调函数helper->handler.packet(helper->cbparam, helper->ptr, helper->size, helper->timestamp, helper->__flags | (helper->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0)),可以看出都是从helper缓存里取的,用户的回调函数,设置的工作是进行写文件(写文件是要注意一定要注意加上adts header),这样才能够正确打开。源码如下:

int rtp_payload_onframe(struct rtp_payload_helper_t *helper){ int r; r = 0; if (helper->size > 0#if !defined(RTP_ENABLE_COURRUPT_PACKET) && 0 == helper->lost#endif ) { // previous packet done r = helper->handler.packet(helper->cbparam, helper->ptr, helper->size, helper->timestamp, helper->__flags | (helper->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0)); // RTP_PAYLOAD_FLAG_PACKET_LOST: miss helper->__flags &= ~RTP_PAYLOAD_FLAG_PACKET_LOST; // clear packet lost flag } // set packet lost flag on next frame if(helper->lost) helper->__flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; // new frame start helper->lost = 0; helper->size = 0; return r;}


3.总结

本篇文章主要讲解了rtp封包的更细节部分,封包格式详细说明,还结合源码更详细的解释真个打包和拆包的过程。希望能够帮助到大家。欢迎关注,收藏,转发,分享。

后期关于项目知识,也会更新在微信公众号“记录世界 from antonio”,欢迎关注