通过UDP发送音频时,如何减少延迟



我编写了一个小型java程序(pc)和android应用程序,旨在将音频从pc流式传输到我的手机。我通过UDP发送音频数据,在确定延迟的来源时遇到了问题。从客户端到服务器,在处理数据包之前大约需要1.5-2秒。

我的程序缓冲512字节发送到客户端,接收后立即播放。

我测试了一些不同的配置,任何(实质上)高于此值的配置,延迟都只会增加。对于较低的值,在延迟方面没有明显的改善,但存在明显的质量损失。

根据windows,设备之间的ping时间只有3ms,所以我认为网络连接不是问题所在,尽管我不确定。

我的客户端(PC)的代码如下所示。

byte[] buffer = new byte[512];
while (true) {
try {
audioInput.read(buffer, 0, buffer.length);
DatagramPacket data = new DatagramPacket(buffer, buffer.length, address, port);
dout.send(data);
System.out.println("Sent Packet #" + i++);
} catch (IOException e) {
e.printStackTrace();
}
}

服务器(电话)的代码如下。

byte[] buffer = new byte[512];
DatagramPacket incomingPacket = new DatagramPacket(buffer, buffer.length);
while (true) {
try {
dgs.receive(incomingPacket);
buffer = incomingPacket.getData();
audioOutput.write(buffer, 0, buffer.length);
} catch (IOException e) {
e.printStackTrace();
}
}

我期望分组以更接近<5ms,但实际上只有在约1500ms后才能接收到它们。

我希望你们中的一些人可能对这类问题有经验。我知道像Discord和Skype这样的应用程序以更高的比特率进行流式传输,延迟要高得多,但延迟要低得多,所以我希望我可能错过了一些东西。

您正在尝试进行实时流传输。让我们看看整个过程中的延迟预算。

首先,您使用512字节的缓冲区——假设样本为floats,采样率为44.1k,则每次处理128个样本,这就得到了~2.9ms的缓冲期。

在这个缓冲区大小下,你能得到的最好的是~7.5ms

控制流程:

1:音频输入硬件生成样本并将其存储在缓冲器中。在预定数量的采样之后,操作系统被中断以提供音频服务。该缓冲周期可以明显长于128个样本。这是T1

2:操作系统安排音频守护进程运行。运行前等待S1

3:音频守护进程运行,处理音频,将其写入缓冲区,并向操作系统发出可以读取样本的信号。这段时间短得可以忽略不计。

4:操作系统通过取消阻止对audioInput.read(buffer, 0, buffer.length);的调用来安排您的输入进程运行。它等待S2才能得到调度。

5:您拨打System.out.println()。这可能会阻塞——尤其是当你每秒向它写入约350次时——可能直到安排了另一个无关的进程。S3

6:UDP被写入网络套接字。操作系统可能会将其排队等待传输——这很可能在之后的短时间内发生

7:通过网络传输。T2

接收端与上述情况大致相反。

因此,总延迟为

2 * (T1 + S1 + S2 + S3 + T2)

我想您的大部分延迟是发送和接收时的硬件缓冲期-T1。如果你得到比10ms低得多的值,S1,S2,S3将开始变得显著。

注:

  • S1S2是操作调度延迟,并且取决于系统负载和调度策略。音频渲染处理程序通常以实时threa优先级运行
  • 您可以通过不登录控制台来消除S3。这种延迟特别不可预测
  • Java运行时可能会带来一些隐藏的运行时成本(例如GC)。这将是可靠的低延迟音频的一个限制因素

真正低延迟的秘诀是:

  • 在C或C中实现++
  • 没有内存分配,正在登录渲染循环
  • 固定堆栈和堆页以防止它们被交换
  • 以实时调度优先级运行音频渲染线程
  • 防止优先级反转的无锁定数据结构

最新更新