【音视频】RTP解包H264 代码实现_android 通用rtp解码成h264
一、概述
之前实现了RTP封包H264,这里实现手动解包H264,并且将解包后的H264保存为本地文件,保存后的H264文件可以直接播放
通过ffplay直接可以播放
ffplay out.h264
我们这里的解包,是做的模拟,并没有监听UDP端口,然后接受RTP包,而是直接从发送端拷贝一份RTP包过来,因此我们这里并没有做UDP的乱序排序,以及丢包检测等,后续如果要做网络接收RTP报文,这些都是要优化的。
二、实现流程
读取文件
- 这一步和之前的封包是一样的,不做讲解
FILE *bits = open_bitstream_file(\"1.h264\");//打开264文件,并将文件指针赋给bits,在此修改文件名实现打开别的264文件。if(!bits){printf(\"open file failed\\n\");return -1;}FILE *out_file = fopen(\"out.h264\", \"wb\");if(!out_file){printf(\"open out_file failed\\n\");return -1;}
open_bitstream_file
函数如下
FILE *open_bitstream_file (char *filename){ FILE *bits = NULL; if (NULL == (bits=fopen(filename, \"rb\"))) { printf(\"open file error\\n\"); } return bits;}
rtp_h264_test_t
结构体
rtp_h264_test_t
这个结构体,我们现在关注的是解包部分- decoder_h264 为H264的解码器,主要是读取对应的H264的NAL结构
out_file
主要是输出的h264的文件指针,用于保存RTP包中的H264内容,写到本地
struct rtp_h264_test_t{ int payload; // payload type const char* encoding; // 音频、视频的格式,比如H264, 该工程只支持264 int fd; struct sockaddr_in addr; size_t addr_size; char *in_file_name; // H264文件名 FILE* in_file; // H264裸流文件 float frame_rate; // 帧率 void* encoder_h264; // 封装 char *out_file_name; FILE *out_file; void* decoder_h264; // 解封装 uint8_t sps[40]; int sps_len; uint8_t pps[10]; int pps_len; int got_sps_pps;};
rtp_payload_t
结构体
rtp_payload_t
结构体主要是存储一些解包时的回调函数,把解包后的H264数据保存到本地中
struct rtp_payload_t // 用来封装回调函数的结构体{ void* (*alloc)(void* param, int bytes); void (*free)(void* param, void *packet); /// @return 0-ok, other-error 拿到一帧完整的数据 int (*packet)(void* param, const void *packet, int bytes, uint32_t timestamp, int flags);};
- 注册这三个回调函数
struct rtp_payload_t handler_rtp_decode_h264;handler_rtp_decode_h264.alloc = rtp_alloc;handler_rtp_decode_h264.free = rtp_free;handler_rtp_decode_h264.packet = rtp_decode_packet;
- 这里主要看看
packet
回调函数,了解它是怎么保存的 - 这里绑定为
rtp_decode_packet
函数,是对H264和AAC通用的结构,我们这里主要看H264 - 可以看到,我们对读取到RTP数据,提取对应的H264裸数据,因为RTP传输的H264数据都是裸数据,包括NAL头部和h264的裸数据,因此我们要手动添加startcode
- 这里如果是FU-A分片发送的H264,在这里已经是组装好的了,因此这个回调函数处理的就是将完整的NAL单元加上startcode,然后写入文件而已
- 至于组装NALU分片的话,不是这个回调函数实现的,具体看后面讲解
static int rtp_decode_packet(void* param, const void *packet, int bytes, uint32_t timestamp, int flags){ static const uint8_t start_code[4] = { 0, 0, 0, 1 }; struct rtp_h264_test_t* ctx = (struct rtp_h264_test_t*)param; static uint8_t buffer[2 * 1024 * 1024]; assert(bytes + 4 < sizeof(buffer)); assert(0 == flags); size_t size = 0; if (0 == strcmp(\"H264\", ctx->encoding) || 0 == strcmp(\"H265\", ctx->encoding)) { memcpy(buffer, start_code, sizeof(start_code)); size += sizeof(start_code); } else if (0 == strcasecmp(\"mpeg4-generic\", ctx->encoding)) { int len = bytes + 7; uint8_t profile = 2; uint8_t sampling_frequency_index = 4; uint8_t channel_configuration = 2; buffer[0] = 0xFF; /* 12-syncword */ buffer[1] = 0xF0 /* 12-syncword */ | (0 << 3)/*1-ID*/ | (0x00 << 2) /*2-layer*/ | 0x01 /*1-protection_absent*/; buffer[2] = ((profile - 1) << 6) | ((sampling_frequency_index & 0x0F) << 2) | ((channel_configuration >> 2) & 0x01); buffer[3] = ((channel_configuration & 0x03) << 6) | ((len >> 11) & 0x03); /*0-original_copy*/ /*0-home*/ /*0-copyright_identification_bit*/ /*0-copyright_identification_start*/ buffer[4] = (uint8_t)(len >> 3); buffer[5] = ((len & 0x07) << 5) | 0x1F; buffer[6] = 0xFC | ((len / 1024) & 0x03); size = 7; } memcpy(buffer + size, packet, bytes); size += bytes; printf(\"nalu get -> bytes:%d, timestamp:%u\\n\", size, timestamp); // TODO: // check media file fwrite(buffer, 1, size, ctx->out_file);}
rtp_payload_decode_input
函数
int rtp_payload_decode_input(void* decoder, const void* packet, int bytes){ struct rtp_payload_delegate_t* ctx; ctx = (struct rtp_payload_delegate_t*)decoder; return ctx->decoder->input(ctx->packer, packet, bytes);}
- 这个函数主要是调用代理类的解码器,然后进行解码操作的,具体看下面的
rtp_payload_delegate_t
结构体
struct rtp_payload_delegate_t // 代理结构体{ struct rtp_payload_encode_t* encoder; struct rtp_payload_decode_t* decoder; void* packer;};
- 因为我们是做的模拟解包,所以直接在编码后直接将编码好的RTP解包(不通过网络传输)
// 拿到一帧RTP序列化后的数据static int rtp_encode_packet(void* param, const void *packet, int bytes, uint32_t timestamp, int flags){ struct rtp_h264_test_t* ctx = (struct rtp_h264_test_t*)param; int ret = 0; ret = sendto( ctx->fd, (void*)packet, bytes, 0, (struct sockaddr*)&ctx->addr, ctx->addr_size); if (ret == -1) { #ifdef WIN32 printf(\"sendto failed: %d\\n\", WSAGetLastError()); #else perror(\"sendto failed\"); #endif return -1; } uint8_t *nalu = (uint8_t *)packet; printf(\"rtp send packet -> nalu_type:%d,0x%02x,0x%02x, bytes:%d, timestamp:%u\\n\", nalu[12]&0x1f, nalu[12], nalu[13], bytes, timestamp); ret = rtp_payload_decode_input(ctx->decoder_h264, packet, bytes); // 解封装 return 0;}
rtp_payload_decode_t
结构体
struct rtp_payload_decode_t{ void* (*create)(struct rtp_payload_t *handler, void* param); void (*destroy)(void* packer); /// RTP packet to PS/H.264 Elementary Stream /// @param[in] decoder RTP packet unpackers /// @param[in] packet RTP packet /// @param[in] bytes RTP packet length in bytes /// @param[in] time stream UTC time /// @return 1-packet handled, 0-packet discard, <0-failed int (*input)(void* decoder, const void* packet, int bytes);};
- 这个结构体才是处理解包的具体函数,负责处理RTP包的,然后解包成H264包,给刚才的回调函数使用的
- 具体看的是
input
回调函数,绑定的回调函数是rtp_h264_unpack_input
函数
struct rtp_payload_decode_t *rtp_h264_decode(){ static struct rtp_payload_decode_t unpacker = { rtp_h264_unpack_create, rtp_h264_unpack_destroy, rtp_h264_unpack_input, }; return &unpacker;}
rtp_h264_unpack_input
函数
- 这个函数是解包的重点,下面看一下它的实现
static int rtp_h264_unpack_input(void* p, const void* packet, int bytes){ int r; uint8_t nalt; struct rtp_packet_t pkt; struct rtp_decode_h264_t *unpacker; unpacker = (struct rtp_decode_h264_t *)p; // 反序列化 if(!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) return -EINVAL; if (-1 == unpacker->flags) { unpacker->flags = 0; unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost } if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; unpacker->size = 0; // discard previous packets } unpacker->seq = (uint16_t)pkt.rtp.seq; nalt = ((unsigned char *)pkt.payload)[0]; switch(nalt & 0x1F) { case 0: // reserved case 31: // reserved // 这里最好是报错 然后返回错误值 assert(0); return 0; // packet discard case 24: // STAP-A return rtp_h264_unpack_stap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); case 25: // STAP-B return rtp_h264_unpack_stap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1); case 26: // MTAP16 return rtp_h264_unpack_mtap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 2); case 27: // MTAP24 return rtp_h264_unpack_mtap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 3); case 28: // FU-A return rtp_h264_unpack_fu(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); case 29: // FU-B return rtp_h264_unpack_fu(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1); default: // 1-23 NAL unit r = unpacker->handler.packet(unpacker->cbparam, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, unpacker->flags); unpacker->flags = 0; unpacker->size = 0; return 0 == r ? 1 : r; // packet handled }}
- 我们做的就是先读取出这个RTP报文的头部,反序列化出来
unpacker = (struct rtp_decode_h264_t *)p;// 反序列化if(!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1)return -EINVAL;
- 反序列化函数
rtp_packet_deserialize
如下:
int rtp_packet_deserialize(struct rtp_packet_t *pkt, const void* data, int bytes){ uint32_t i, v; int hdrlen; const uint8_t *ptr; if (bytes < RTP_FIXED_HEADER) // RFC3550 5.1 RTP Fixed Header Fields(p12) return -1; ptr = (const unsigned char *)data; memset(pkt, 0, sizeof(struct rtp_packet_t)); // pkt header v = nbo_r32(ptr); pkt->rtp.v = RTP_V(v); pkt->rtp.p = RTP_P(v); pkt->rtp.x = RTP_X(v); pkt->rtp.cc = RTP_CC(v); pkt->rtp.m = RTP_M(v); pkt->rtp.pt = RTP_PT(v); pkt->rtp.seq = RTP_SEQ(v); pkt->rtp.timestamp = nbo_r32(ptr + 4); pkt->rtp.ssrc = nbo_r32(ptr + 8); assert(RTP_VERSION == pkt->rtp.v); // 调试的时候用 hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4; // 解析带csrc时的总长度 if (RTP_VERSION != pkt->rtp.v || bytes < hdrlen + (pkt->rtp.x ? 4 : 0) + (pkt->rtp.p ? 1 : 0)) return -1; // 报错 // pkt contributing source for (i = 0; i < pkt->rtp.cc; i++) { pkt->csrc[i] = nbo_r32(ptr + 12 + i * 4); } assert(bytes >= hdrlen); pkt->payload = (uint8_t*)ptr + hdrlen; // 跳过头部 拿到payload pkt->payloadlen = bytes - hdrlen; // payload长度 // pkt header extension if (1 == pkt->rtp.x) { const uint8_t *rtpext = ptr + hdrlen; assert(pkt->payloadlen >= 4); pkt->extension = rtpext + 4; pkt->reserved = nbo_r16(rtpext); pkt->extlen = nbo_r16(rtpext + 2) * 4; if (pkt->extlen + 4 > pkt->payloadlen) { assert(0); return -1; } else { pkt->payload = rtpext + pkt->extlen + 4; pkt->payloadlen -= pkt->extlen + 4; } } // padding if (1 == pkt->rtp.p) { uint8_t padding = ptr[bytes - 1]; if (pkt->payloadlen < padding) { assert(0); return -1; } else { pkt->payloadlen -= padding; } } return 0;}
回顾一下RTP的报文格式:
- 这是它的报文头部
- 那么我们就可以根据这个解包出RTP报文的头部了,这个是固定的
- 如果有扩展位,我们还需要解析扩展字节
详细的解析如下
一、整体流程概述
- 参数校验:确保输入数据长度不小于 RTP 固定头部长度(12 字节)。
- 解析固定头部:提取版本号、填充标志、扩展标志、CSRC 计数器等核心字段。
- 处理 CSRC 列表:根据 CC 值解析 0-15 个贡献源标识符。
- 处理头部扩展(如果存在):解析扩展头的长度和内容。
- 处理填充数据(如果存在):移除填充字节,计算真实负载长度。
- 定位负载位置:设置负载指针和长度。
二、关键数据结构与协议字段
RTP 固定头部格式(RFC 3550):
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|V=2|P|X| CC |M| PT | sequence number |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| timestamp |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| synchronization source (SSRC) identifier |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+| contributing source (CSRC) identifiers || .... |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- V:版本号(固定为 2)
- P:填充标志(是否包含填充字节)
- X:扩展标志(是否包含头部扩展)
- CC:CSRC 计数器(0-15 个贡献源)
- M:标记位(由应用层定义)
- PT:负载类型(如 H.264=96,AAC=97 等)
- Sequence Number:序列号(用于排序和丢包检测)
- Timestamp:时间戳(用于同步)
- SSRC:同步源标识符(标识媒体流源)
三、代码详细解析
1. 参数校验与初始化
if (bytes < RTP_FIXED_HEADER) // RTP_FIXED_HEADER = 12 return -1;ptr = (const unsigned char *)data;memset(pkt, 0, sizeof(struct rtp_packet_t));
- 检查数据长度是否至少包含 RTP 固定头部(12 字节)。
- 初始化输出结构体
pkt
。
2. 解析固定头部字段
v = nbo_r32(ptr); // 读取前4字节(网络字节序转主机序)pkt->rtp.v = RTP_V(v); // 版本号 (2 bits)pkt->rtp.p = RTP_P(v); // 填充标志 (1 bit)pkt->rtp.x = RTP_X(v); // 扩展标志 (1 bit)pkt->rtp.cc = RTP_CC(v); // CSRC计数器 (4 bits)pkt->rtp.m = RTP_M(v); // 标记位 (1 bit)pkt->rtp.pt = RTP_PT(v); // 负载类型 (7 bits)pkt->rtp.seq = RTP_SEQ(v); // 序列号 (16 bits)pkt->rtp.timestamp = nbo_r32(ptr + 4); // 时间戳pkt->rtp.ssrc = nbo_r32(ptr + 8); // SSRC
- 使用位运算提取各字段(例如,
RTP_V(v)
可能是(v >> 30) & 0x3
)。 - 验证版本号是否为 2(RTP 标准版本)。
3. 计算头部长度并校验
hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4; // 基础长度 + CSRC列表长度if (RTP_VERSION != pkt->rtp.v || bytes < hdrlen + ...) return -1;
- 每个 CSRC 占 4 字节,总头部长度为
12 + CC*4
。 - 检查数据长度是否足够包含完整头部。
4. 解析 CSRC 列表
for (i = 0; i < pkt->rtp.cc; i++){ pkt->csrc[i] = nbo_r32(ptr + 12 + i * 4);}
- CSRC 列表紧跟在固定头部之后,每个 CSRC 为 32 位(4 字节)。
5. 定位负载位置
pkt->payload = (uint8_t*)ptr + hdrlen;pkt->payloadlen = bytes - hdrlen;
- 初始负载位置为头部之后的字节。
- 初始负载长度为总长度减去头部长度。
6. 处理头部扩展(如果存在)
if (1 == pkt->rtp.x){ const uint8_t *rtpext = ptr + hdrlen; pkt->extension = rtpext + 4; pkt->reserved = nbo_r16(rtpext); // 保留字段 pkt->extlen = nbo_r16(rtpext + 2) * 4; // 扩展长度(以32位为单位) pkt->payload = rtpext + pkt->extlen + 4; pkt->payloadlen -= pkt->extlen + 4;}
- 扩展头格式:
- 16 位保留字段
- 16 位扩展长度(表示 32 位字的数量,需乘以 4 得到字节数)
- 扩展内容
7. 处理填充数据(如果存在)
if (1 == pkt->rtp.p){ uint8_t padding = ptr[bytes - 1]; pkt->payloadlen -= padding;}
- 填充字节的最后一个字节表示填充长度(包括自身)。
- 从负载长度中减去填充字节数。
然后我们需要根据发送的RTP报文的类型,来进行对应的H264解析,比如SPS、PPS可能就是直接发送一整个RTP包出去,而I帧可能就是通过FU-A的方式分片发送,如题类型参考下面:
代码实现如下:
nalt = ((unsigned char *)pkt.payload)[0];switch(nalt & 0x1F){case 0: // reservedcase 31: // reserved// 这里最好是报错 然后返回错误值assert(0);return 0; // packet discardcase 24: // STAP-Areturn rtp_h264_unpack_stap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0);case 25: // STAP-Breturn rtp_h264_unpack_stap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1);case 26: // MTAP16return rtp_h264_unpack_mtap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 2);case 27: // MTAP24return rtp_h264_unpack_mtap(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 3);case 28: // FU-Areturn rtp_h264_unpack_fu(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0);case 29: // FU-Breturn rtp_h264_unpack_fu(unpacker, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1);default: // 1-23 NAL unitr = unpacker->handler.packet(unpacker->cbparam, (const uint8_t*)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, unpacker->flags);unpacker->flags = 0;unpacker->size = 0;return 0 == r ? 1 : r; // packet handled}
-
如果是1-23的NALU类型,那么我们直接调用之前说的回调函数,加入startcode之后,然后保存H264即可,这里NALU的头部和H264的数据直接从报文就可以读出了
-
如果是FU-A类型的,我们需要进行NALU的组包,看
rtp_h264_unpack_fu
函数,如下
static int rtp_h264_unpack_fu(struct rtp_decode_h264_t *unpacker, const uint8_t* ptr, int bytes, uint32_t timestamp, int fu_b){ int r, n; uint8_t fuheader; //uint16_t don; r = 0; n = fu_b ? 4 : 2; // payload前面有几个字节是供fu_a或fu_b使用, fu_a只有2个字节; fu_b是4个字节 if (bytes < n || unpacker->size + bytes - n > RTP_PAYLOAD_MAX_SIZE) { assert(0); return -EINVAL; // error } if (unpacker->size + bytes - n + 1 /*NALU*/ > unpacker->capacity) { void* p = NULL; int size = unpacker->size + bytes + 1; size += size / 4 > 128000 ? size / 4 : 128000; p = realloc(unpacker->ptr, size); if (!p) { // set packet lost flag unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; unpacker->size = 0; return -ENOMEM; // error } unpacker->ptr = (uint8_t*)p; unpacker->capacity = size; } fuheader = ptr[1]; //don = nbo_r16(ptr + 2); if (FU_START(fuheader)) {#if 0 if (unpacker->size > 0) { unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); unpacker->flags = 0; unpacker->size = 0; // reset }#endif unpacker->size = 1; // NAL unit type byte unpacker->ptr[0] = (ptr[0]/*indicator*/ & 0xE0) | (fuheader & 0x1F); assert(H264_NAL(unpacker->ptr[0]) > 0 && H264_NAL(unpacker->ptr[0]) < 24); } else { if (0 == unpacker->size) { unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; return 0; // packet discard } assert(unpacker->size > 0); } unpacker->timestamp = timestamp; if (bytes > n) { assert(unpacker->capacity >= unpacker->size + bytes - n); memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n); unpacker->size += bytes - n; } if(FU_END(fuheader)) { if(unpacker->size > 0) // 多次传入数据后等到FU_END的时候难道一个完整的nalu r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); unpacker->flags = 0; unpacker->size = 0; // reset } return 0 == r ? 1 : r; // packet handled}
回顾FU-A的RTP报文格式
- 前面的RTP报文都是一样的,只是负载的部分头部不是直接的NALU头部,而是使用FU indication + FU-header两部分
- 整体结构变成了FU indication + FU-header + H264的NALU分片裸数据,这三部分组成
- 具体的负载头部报文格式如下
FU indication
+---------------+|0|1|2|3|4|5|6|7|+-+-+-+-+-+-+-+-+|F|NRI| Type |+---------------+
-
这⾥⾯的的F和NRI已经在NALU的Header解释清楚了,就是NALU头的前⾯三个bit位,后⾯的TYPE就是NALU的FU-A类型28,这样在RTP固定头后⾯第⼀字节的后⾯5bit提取出来就确认了该RTP包承载的不是⼀个完整的NALU,是其⼀部分。
-
那么问题来了,⼀个NALU切分成多个RTP包传输,那么到底从哪⼉开始哪⼉结束呢?可能有⼈说RTP包固定头不是有mark标记么,注意区分那个是以帧图像的结束标记,这⾥要确定是NALU结束的标记,其次NALU的类型呢?那么就需要RTP固定12字节后⾯的Fu Header来进⾏区分。
FU header
+---------------+|0|1|2|3|4|5|6|7|+-+-+-+-+-+-+-+-+|S|E|R| Type |+---------------+
字段解释:
- S: 1 bit 当设置成1,开始位指示分⽚NAL单元的开始。当跟随的FU荷载不是分⽚NAL单元荷载的开始,开始位设为0。
- E: 1 bit 当设置成1, 结束位指示分⽚NAL单元的结束,即, 荷载的最后字节也是分⽚NAL单元的最后⼀个字节,当跟随的FU荷载不是分⽚NAL单元的最后分⽚,结束位设置为0。也就是说⼀个NALU切⽚时,第⼀个切⽚的SE是10,然后中间的切⽚是00,最后⼀个切⽚时11。
- R: 1 bit保留位必须设置为0,接收者必须忽略该位。
- Type: 5 bits此处的Type就是NALU头中的Type,取1-23的那个值,表示 NAL单元荷载类型定义
- 我们假定RTP是按顺序发来的,并且不会丢包,因此我们直接判断FU Header的标志位
fuheader = ptr[1];
- 如果标志位S为1,说明是一个NALU的开始
if (FU_START(fuheader)) {#if 0 if (unpacker->size > 0) { unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); unpacker->flags = 0; unpacker->size = 0; // reset }#endif unpacker->size = 1; // NAL unit type byte unpacker->ptr[0] = (ptr[0]/*indicator*/ & 0xE0) | (fuheader & 0x1F); assert(H264_NAL(unpacker->ptr[0]) > 0 && H264_NAL(unpacker->ptr[0]) < 24); }
- 如果标志位E为1,说明是一个NALU的结束,这是我们组包结束,然后直接使用上层回调函数保存为H264到本地文件
if(FU_END(fuheader)) { if(unpacker->size > 0) // 多次传入数据后等到FU_END的时候难道一个完整的nalu r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); unpacker->flags = 0; unpacker->size = 0; // reset }
- 如果这两个标志位为0,那我们就直接组包就好,将数据拼到缓冲区后面
if (bytes > n){assert(unpacker->capacity >= unpacker->size + bytes - n);memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n);unpacker->size += bytes - n;}
更多资料:https://github.com/0voice