java - 通过 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();
}
}
我预计数据包的到达速度接近小于 5 毫秒的网络延迟,但实际上仅在约 1500 毫秒后才收到它们。
我希望你们中的一些人可能对这类问题有经验。我知道像 Discord 和 Skype 这样的应用程序以更高的比特率进行流传输,延迟更高,但延迟却大大降低,所以我希望我可能错过了一些东西。
解决方案
您正在尝试进行实时流式传输。让我们看看整个过程的延迟预算。
首先,您正在使用 512 字节的缓冲区 - 假设样本为float
s 且采样率为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
将开始变得重要。
笔记:
S1
,S2
是操作调度延迟并且依赖于系统负载和调度策略。音频渲染处理程序通常以实时威胁优先级运行。- 您可以
S3
通过不登录控制台来消除。这种延迟是特别不可预测的。 - Java 运行时可能会带来一些隐藏的运行时成本(例如 GC)。这将是可靠的低延迟音频的限制因素。
真正低延迟的秘诀是:
- 用 C 或 C++ 实现
- 没有内存分配,登录渲染循环
- 固定堆栈和堆页面以防止它们被交换
- 以实时调度优先级运行音频渲染线程。
- 防止优先级倒置的无锁数据结构。
推荐阅读
- web-deployment - JBOSS 7.3 ear部署错误(根元素之前的文档中的标记必须格式正确)
- python - 如何找到颜色的“上”和“下”范围?
- pandas - 如何使用符合 PEP8 的条件过滤 Pandas 数据框?
- c# - 年数总和折旧 (SYD) 调试
- python - 单击烧瓶中的提交没有任何反应(FlaskForm)
- google-analytics - 为什么在 Google Analytics(分析)中没有添加到购物车的会话,但有结帐的会话?
- c# - 默认日期时间值显示在日期时间列中随机记录的数据表中,即使我没有在任何地方分配值
- mysql - 连接查询不返回 CURDATE 的记录
- r - 在高程上使用传单或在 R 闪亮中绘制彩色 gpx 轨迹
- javascript - 无法从 Firebase 存储 (Javascript) 获取下载链接