OpenRTP 开源地址
暂时使用h264 + aac 的音频去测试,点开配置去选择
1 音视频同步问题
先要解决一个音视频同步问题,否则包排不排序都不对,这是因为视频时间戳不一定能够对上音频,为什么呢?因为大部分摄像头不一定能够达到采样的帧率,而音频大部分时间都是用回调来进行,是比较正确的,时间戳递增可以准确无误,但视频一旦帧率没有够上,结果递增时间却是确定的,就会造成两者时间戳不同步,一般像摄像头这种设备,无法够上足够的帧率,所以做法有两种。
1 插帧服务
2 保证帧率
3 修改时间戳
按照绝对时间来修改时间戳是可以的
c
static uint32_t convertToRTPTimestamp(/*struct timeval tv*/)
{
timeval tv;
gettimeofday(&tv, NULL);
UINT32 timestampIncrement = (90000 * tv.tv_sec);
timestampIncrement += (UINT32)((2.0 * 90000 * tv.tv_usec + 1000000.0) / 2000000);
//UINT32 const rtpTimestamp = timestampIncrement;
return timestampIncrement;
}
这样视频时间戳和音频时间戳就可以同步,下面把rtp包的乱序进行排序,基础udp协议是不一定序号连续还有可能重复包发送,所以要解决这个问题,当然,和排序无关,udp丢包是不可避免的。
2 udp 乱序排包
udp 接收一般是乱序的,如何来进行包排序呢
定义数据结构
c
struct Packet
{
uint16_t s = 0;
uint32_t t = 0;
void* real_rtp = NULL;
int timesleep = 10;
long long receiveTime = 0;
long long sendTime = 0;
Packet(uint16_t seq, uint32_t ts)
{
s = seq;
t = ts;
}
void SetCurrentTime(int64_t timere, int64_t timese)
{
receiveTime = timere;
sendTime = timese;
}
};
其中real_rtp 存放RTP包,从中获取rtp 时间戳等
由于时间戳是32位,而rtp协议的sequnce num 是16位无符号证书,所以无法按照正常直接的比较来进行排序,需要回环计算,为了插入和删除方便,定义双缓冲,一个缓冲为queue队列,输出给应用,一个缓冲为链表, 方便排序
c
class c_jitter
{
//源端口
uint16_t v_port = 0;
std::list<Packet*> v_packets;
//如果时间戳大于最后1个包1秒,将前面所有的包全部播放掉,插入包 返回
//最后1个包的seq 为 s1 当前包为s2
// 这个包减去最后一个包得到值 s
// if s > 65000 往前继续找
// 等于 删除
// s > 0 < 535 插入
//否则删除
public:
uint16_t out_seq = 0; //出去的包是1
...................
};
其中out_seq 为出去的sequence num 记录,如果再次进来的包排序小于这个sequence num,则必须直接放弃
3 排序算法
3.1 排序基础
排序算法如下,增加port 是因为如果port 源端口变了,实际上以前的所有rtp包都必须放弃,算法的思想是先假定新进来的包是大于最后一个包的seq的,再进行判断16位整数回绕,最后根据是否大于已经出去的seq 数,判决是否放弃还是插入链表
c
int sortPacket(int seq, int ts, uint16_t port)
{
if (v_port == 0)
v_port = port;
if (v_packets.size() > 0) {
bool isin = false;
auto riter = v_packets.rbegin();
while (riter != v_packets.rend()) {
Packet* node = *riter;
uint16_t s1 = node->s;
uint16_t s2 = seq;
uint16_t s = s2 - s1;
if (s == 0) // 重复包
{
return -1;
}
if (s < 535)
{
//插入到这个riter的后面
auto nextIt = riter.base();
Packet* node = new Packet(seq, ts);
v_packets.insert(nextIt, node);
isin = true;
break;
}
++riter;
}
if (!isin)
{
uint16_t s1 = v_packets.front()->s;
uint16_t s2 = seq;
//uint16_t s = s2 - s1;
if (s2 > s1) // like 65535 1 out is 0 then the 65535 we discard
{
uint16_t sx1 = s1 - out_seq;
uint16_t sx2 = s1 - s2;
if (sx1 > sx2)
{
//uint16_t s0 = out_seq -s1;
printf("s2>s1 seq in %d seq out is %d\n", s2, out_seq);
auto it = v_packets.begin();
// 在第一个元素之前插入新元素
Packet* node = new Packet(seq, ts);
v_packets.insert(it, node);
return 0;
}
}
if (s2 < s1) //like 1 3 out is 2
{
uint16_t sx1 = s1 - out_seq;
uint16_t sx2 = s1 - s2;
if (sx2 < sx1)
{
printf("s2<s1 seq in %d out is %d\n", s2, out_seq);
auto it = v_packets.begin();
// 在第一个元素之前插入新元素
Packet* node = new Packet(seq, ts);
v_packets.insert(it, node);
return 0;
}
}
}
return -1;
}
//是第一个元素
auto it = v_packets.begin();
// 在第一个元素之前插入新元素
Packet* node = new Packet(seq, ts);
v_packets.insert(it, node);
return 0;
}
3.2 测试
c
void test1()
{
uint16_t a1 = 65530;
uint16_t a2 = 65535;
//uint16_t b = 0;
uint16_t b1 = 3;
uint16_t b2 = 4;
uint16_t b3 = 65531;
uint16_t b4 = 7;
uint16_t b5 = 6;
uint16_t b6 = 65533;
uint16_t b7 = 1;
uint16_t b8 = 2;
uint16_t b9 = 0;
c_jitter p;
p.addPacket(a1, 0);
p.addPacket(a2, 0);
p.addPacket(b1, 0);
p.addPacket(b2, 0);
p.sortPacket(b3, 0, 6000);
p.sortPacket(b4, 0, 6000);
p.sortPacket(b5, 0, 6000);
p.sortPacket(b6, 0, 6000);
p.sortPacket(b7, 0, 6000);
p.sortPacket(b8, 0, 6000);
p.sortPacket(b9, 0, 6000);
p.printPacketList();
}
按照 65530 65535 3 4 65531 7 6 65533 1 2 0 排序的结果应该为
65530 65531 65533 65535 0 1 2 3 4 6 7
结果为
如果出去的包的seq 为 0
插入包为 6 8 7 7 15 3 65535 65534
则因为出去的包为0 ,而最后两个包虽然接近于3 ,但是 小于 0 ,所以必须被丢弃,而 两个7 包也必须丢弃一个
c
void test3()
{
c_jitter p;
//p.addPacket(7, 0);
p.setout_Seq(0);
p.sortPacket(6, 0, 6000);
p.sortPacket(8, 0, 6000);
p.sortPacket(7, 0, 6000);
p.sortPacket(7, 0, 6000);
p.sortPacket(15, 0, 6000);
p.sortPacket(3, 0, 6000);
p.sortPacket(65535, 0, 6000);
p.sortPacket(65534, 0, 6000);
p.printPacketList();
}
结果为
4 包抖动
4.1 、数据收集
记录每个数据包的接收时间戳。
可以在接收数据包时,使用系统时间函数获取当前时间并记录下来。
4.2 计算延迟
对于每个接收到的数据包,计算其延迟。
延迟可以通过当前时间减去数据包的发送时间(如果发送时间包含在数据包中或者可以通过其他方式获取)得到。
4.3 计算抖动
首先计算平均延迟。
将所有数据包的延迟相加,然后除以数据包的数量。
对于每个数据包,计算其延迟与平均延迟的差值的绝对值。
这个差值表示该数据包的延迟与平均延迟的偏离程度。
计算抖动值。
抖动可以通过计算所有数据包延迟与平均延迟差值的绝对值的平均值来得到。
c
//计算抖动
double calculateJitter(const std::list<Packet*>& packets) {
int totalDelay = 0;
for (const Packet* packet : v_packets) {
int delay = packet->receiveTime - packet->sendTime;
totalDelay += delay;
}
double averageDelay = static_cast<double>(totalDelay) / packets.size();
double totalDeviation = 0.0;
for (const Packet* packet : packets) {
int delay = packet->receiveTime - packet->sendTime;
double deviation = std::abs(delay - averageDelay);
totalDeviation += deviation;
}
return totalDeviation / packets.size();
}
};
程序已经放在开源项目里面,为了增加可用性,后面会加上我们的rtmp server 和 rtspserver,同时使用tcp 和 udp。
4.4 根据抖动调整延时
根据以上的抖动,可以动态去分配延时,尽量让rtp包延时均匀,如果包来的越来越慢,抖动加剧,我们的策略应该适当来进行延时播放。