1. sps 及pps 封装格式:
void yang_getConfig_Meta_H264( YangSample* psps, YangSample* ppps,uint8_t *configBuf,int32_t *p_configLen){
//type_codec1 + avc_type + composition time + fix header + count of sps + len of sps + sps + count of pps + len of pps + pps
// int32_t nb_payload = 1 + 1 + 3 + 5 + 1 + 2 + sps->size + 1 + 2 + pps->size;
int32_t spsLen=psps->nb;
int32_t ppsLen=ppps->nb;
uint8_t* sps=(uint8_t*)psps->bytes;
uint8_t* pps=(uint8_t*)ppps->bytes;
configBuf[0] = 0x17;
configBuf[1] = 0x00;
configBuf[2] = 0x00;
configBuf[3] = 0x00;
configBuf[4] = 0x00;
configBuf[5] = 0x01;
configBuf[6] = sps[1];//0x42;
configBuf[7] = sps[2];//0xC0;
configBuf[8] = sps[3];//0x29; //0x29; //AVCLevelIndication1f
configBuf[9] = 0xff; //03;//ff;//0x03; AVCLevelIndication
configBuf[10] = 0xe1; //01;//e1;//01;numOfSequenceParameterSets
uint8_t * szTmp = configBuf + 11;
yang_put_be16((char*) szTmp, (uint16_t) spsLen);
szTmp+=2;
//*szTmp++=0x00;
// *szTmp++=spsLen;
memcpy(szTmp, sps, spsLen);
szTmp += spsLen;
*szTmp = 0x01;
szTmp += 1;
yang_put_be16((char*) szTmp, (uint16_t) ppsLen);
szTmp+=2;
// *szTmp++=0x00;
// *szTmp++=ppsLen;
memcpy(szTmp, pps, ppsLen);
szTmp += ppsLen;
*p_configLen = szTmp - configBuf;
szTmp = NULL;
}
I 及P 帧:四个字节start code 替换成size,size 为I及P 的大小减4。
2. rtp 数据封装
封包介绍:
2.1 RTP 头的结构:
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: RTP协议的版本号,占2bits,当前协议版本号为2
P: 填充标志,占1bit,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
X: 扩展标志,占1bit,如果X=1,则在RTP报头后跟有一个扩展报头
CC: CSRC计数器,占4位,指示CSRC 标识符的个数
M: 1bit,标记解释由设置定义,目的在于允许重要事件在包流中标记出来。如不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
负载类型 Payload type(PT): 7bits
注:rfc里面对一些早期的格式定义了这个payload type。但是后来的,如h264并没有分配,那就用96来代替。因此现在96以上都不表示特定的格式,具体表示什么要用sdp或者其他协议来协商。
序列号 Sequence number(SN): 16bits,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1,序列号的初始值是随机产生的。可以用于检查丢包以及进行数据包排序。
时间戳 Timestamp: 32bits,必须使用90kHz时钟频率。
同步信源(SSRC)标识符: 32bits,用于标识同步信源。该标识符是随机随机产生的,参加同一视频会议的两个同步信源不能有相同的SSRC。
特约信源(CSRC)标识符: 每个CSRC标识符占32bits,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。
2.2 头部FU-indicator格式为.占用一个字节:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
这里的Type类型除1-23外还可取以下值:
24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
如果使用1-23就是:单一NAL单元模式
2.3 单一NAL单元模式
对于 NALU 的长度小于 MTU 大小的包, 一般采用单一 NAL 单元模式.
对于一个原始的 H.264 NALU 单元常由 [Start Code] [NALU Header] [NALU Payload] 三部分组 成, 其中 Start Code 用于标示这是一个
NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", NALU 头仅一个字节, 其后都是 NALU 单元内容.
打包时去除 "00 00 01" 或 "00 00 00 01" 的开始码, 把其他数据封包的 RTP 包即可.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
例:
如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]
这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.
封装成 RTP 包将如下:
[ RTP Header ][FU-indicator] [ 67 42 A0 1E 23 56 0E 2F ]
即只要去掉 4 个字节的开始码就可以了.
2.4 组合封包模式
其次, 当 NALU 的长度特别小时, 可以把几个 NALU 单元封在一个 RTP 包中.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
这里只介绍STAP-A模式,如果是STAP-B的话会多加入一个DON域,另外还有MTAP16、MTAP24,具体不介绍,可以看rfc文档,文章尾贴一个链接可以去看。
如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]
[00 00 00 01 68 42 B0 12 58 6A D4 FF ... ]
封装成 RTP 包将如下:
[ RTP Header ] [78 (STAP-A头,占用1个字节)] [第一个NALU长度 (占用两个字节)] [ 67 42 A0 1E 23 56 0E 2F ] [第二个NALU长度 (占用两个字节)] [68 42 B0 12 58 6A D4 FF ... ]
2.5 分片的单元:
当NALU的长度超过MTU时,就必须对NALU单元进行分片封包.也称为Fragmentation Units(FUs).
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14. RTP payload format for FU-A
The FU indicator octet has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
别被名字吓到这个格式就是上面提到的RTP h264负载类型,Type为FU-A
The FU header has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S bit为1表示分片的NAL开始,当它为1时,E不能为1
E bit为1表示结束,当它为1,S不能为1
R bit保留位
Type就是NALU头中的Type,取1-23的那个值
3. metartc 封包
yang_push_h264_video 根据 数据大小与kRtpMaxPayloadSize 比较,不超过采用单一NAL单元模式。超过采用fu_a 封包模式。
int32_t yang_push_h264_video(void *psession, YangPushH264Rtp *rtp,
YangFrame *videoFrame) {
int32_t err = Yang_Ok;
YangRtcSession *session=(YangRtcSession*)psession;
if (videoFrame->nb <= kRtpMaxPayloadSize) {
if ((err = yang_push_h264_package_single_nalu2(session, rtp, videoFrame))
!= Yang_Ok) {
return yang_error_wrap(err, "package single nalu");
}
session->context.stats.sendStats.videoRtpPacketCount++;
} else {
if ((err = yang_push_h264_package_fu_a(session, rtp, videoFrame,
kRtpMaxPayloadSize)) != Yang_Ok) {
return yang_error_wrap(err, "package fu-a");
}
}
session->context.stats.sendStats.frameCount++;
return err;
}
分析yang_push_h264_package_fu_a 函数。
int32_t yang_push_h264_package_fu_a(YangRtcSession *session, YangPushH264Rtp *rtp,
YangFrame *videoFrame, int32_t fu_payload_size) {
int32_t err = Yang_Ok;
int32_t plen = videoFrame->nb;
uint8_t *pdata = videoFrame->payload;
char *p = (char*) pdata + 1;
int32_t nb_left = plen - 1;
uint8_t header = pdata[0];
uint8_t nal_type = header & kNalTypeMask;
int32_t num_of_packet = ((plen - 1) % fu_payload_size==0)?0:1 + (plen - 1) / fu_payload_size;
for (int32_t i = 0; i < num_of_packet; ++i) {
int32_t packet_size = yang_min(nb_left, fu_payload_size);
yang_reset_rtpPacket(&rtp->videoFuaPacket);
rtp->videoFuaPacket.header.payload_type = YangH264PayloadType;
rtp->videoFuaPacket.header.ssrc = rtp->videoSsrc;
rtp->videoFuaPacket.frame_type = YangFrameTypeVideo;
rtp->videoFuaPacket.header.sequence = rtp->videoSeq++;
rtp->videoFuaPacket.header.timestamp = videoFrame->pts;
rtp->videoFuaPacket.header.marker = (i == num_of_packet - 1) ? 1 : 0;
rtp->videoFuaPacket.payload_type = YangRtspPacketPayloadTypeFUA2;
memset(&rtp->videoFua2Data, 0, sizeof(YangFua2H264Data));
rtp->videoFua2Data.nri = (YangAvcNaluType) header;
rtp->videoFua2Data.nalu_type = (YangAvcNaluType) nal_type;
rtp->videoFua2Data.start = (i == 0) ? 1 : 0;
rtp->videoFua2Data.end = (i == (num_of_packet - 1)) ? 1 : 0;
rtp->videoFua2Data.payload = rtp->videoBuf;
rtp->videoFua2Data.nb = packet_size;
memcpy(rtp->videoFua2Data.payload, p, packet_size);
p += packet_size;
nb_left -= packet_size;
#if Yang_Using_TWCC
if(i==0){
rtp->rtpExtension.twcc.sn=rtp->twccSeq++ ;
rtp->videoFuaPacket.header.extensions=&rtp->rtpExtension;
session->context.twcc.insertLocal(&session->context.twcc.session,rtp->rtpExtension.twcc.sn);
}
#endif
if ((err = yang_push_h264_encodeVideo(session, rtp, &rtp->videoFuaPacket))
!= Yang_Ok) {
return yang_error_wrap(err, "encode packet");
}
rtp->videoFuaPacket.header.extensions=NULL;
}
return err;
}
函数数据:yang_encode_h264_fua2 分包封装及yang_encode_h264_raw single NAL 封装。
int32_t yang_push_h264_encodeVideo(YangRtcSession *session, YangPushH264Rtp *rtp,
YangRtpPacket *pkt) {
int err = 0;
yang_init_buffer(&rtp->buf, yang_get_rtpBuffer(rtp->videoRtpBuffer), kRtpPacketSize);
if ((err = yang_encode_rtpHeader(&rtp->buf, &pkt->header)) != Yang_Ok) {
return yang_error_wrap(err, "rtp header(%d) encode packet fail",
pkt->payload_type);
}
if (pkt->payload_type == YangRtspPacketPayloadTypeRaw) {
err = yang_encode_h264_raw(&rtp->buf, &rtp->videoRawData);
} else if (pkt->payload_type == YangRtspPacketPayloadTypeFUA2) {
err = yang_encode_h264_fua2(&rtp->buf, &rtp->videoFua2Data);
} else if (pkt->payload_type == YangRtspPacketPayloadTypeSTAP) {
err = yang_encode_h264_stap(&rtp->buf, &rtp->stapData);
yang_reset_h2645_stap(&rtp->stapData);
}
if (err != Yang_Ok) {
return yang_error_wrap(err, "rtp payload(%d) encode packet fail",
pkt->payload_type);
}
if (pkt->header.padding_length > 0) {
uint8_t padding = pkt->header.padding_length;
if (!yang_buffer_require(&rtp->buf, padding)) {
return yang_error_wrap(ERROR_RTC_RTP_MUXER,
"padding requires %d bytes", padding);
}
memset(rtp->buf.head, padding, padding);
yang_buffer_skip(&rtp->buf, padding);
}
session->context.stats.on_pub_videoRtp(&session->context.stats.sendStats,pkt,&rtp->buf);
return yang_send_avpacket(session, pkt, &rtp->buf);
}
yang_encode_h264_fua2 组成rtp 数据包;
int32_t yang_encode_h264_fua2(YangBuffer* buf,YangFua2H264Data* pkt){
if (!yang_buffer_require(buf,2 + pkt->nb)) {
return yang_error_wrap(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1);
}
char *p = buf->head; // rtp header 数据包
uint8_t fu_indicate = kFuA;
fu_indicate |= (pkt->nri & (~kNalTypeMask));
*p++ = fu_indicate; // 一个字节fu_indicate
// FU header, @see https://tools.ietf.org/html/rfc6184#section-5.8
uint8_t fu_header = pkt->nalu_type;
if (pkt->start) {
fu_header |= kStart;
}
if (pkt->end) {
fu_header |= kEnd;
}
*p++ = fu_header; // 一个字节fu_header,表示分包,开始-》中间-》结束
// FU payload, @see https://tools.ietf.org/html/rfc6184#section-5.8
memcpy(p, pkt->payload, pkt->nb); // 数据部分
// Consume bytes.
yang_buffer_skip(buf,2 + pkt->nb);
return Yang_Ok;
}