语音聊天,增加延迟(缓冲区)-可以解决吗
本文关键字:解决 缓冲区 增加 延迟 语音聊天 | 更新日期: 2023-09-27 18:27:41
好吧,我正在制作一个语音聊天软件。我正在使用NAudio,一个优秀的图书馆。
但我遇到了一个问题。当发生什么事情时,缓冲区可能会上升。我想这是从一个例子开始的,当操作系统加载了一些东西,语音聊天应用程序被"搁置"了一秒钟。在这段时间里,它会将数据添加到缓冲区中,使当前数据延迟。
由于接球手一直以相同的速度打球,所以总是会被延迟。
现在我有一个"解决方案",就是当缓冲区达到一定长度时清除缓冲区。尽管这一点都不理想,而且更多的是一种技巧而非解决方案。
现在转到代码部分。首先,我初始化我使用的东西。
private NAudio.Wave.WaveInEvent SendStream = new WaveInEvent();
private NAudio.Wave.AsioOut Aut;
private NAudio.Wave.WaveFormat waveformat = new WaveFormat(48000, 16, 2);
private WasapiLoopbackCapture Waloop = new WasapiLoopbackCapture();
private NAudio.Wave.BufferedWaveProvider waveProvider;
waveProvider = new NAudio.Wave.BufferedWaveProvider(waveformat);
waveProvider.DiscardOnBufferOverflow = true;
SendStream.WaveFormat = waveformat;
使用waveformat只是为了让我不必一直重写它。使用DiscardOnBufferOverflow,所以如果我在缓冲区上设置了一定的长度,例如20ms。它将丢弃上面的任何内容,否则将返回一个异常。我认为,如果我不设置长度,它不会起任何作用,默认情况下它可能是无限的。
除此之外,SendStream是一个WaveInEvent,这意味着当我使用DataAvailable时,它将在BackgroundThread上运行。瓦卢普几乎是一样的,除了它是一个环回。在接收部分中使用waveprovider来回放音频。Waveformat是,嗯,Waveform,设置它很重要,并且至少在我的应用程序中具有相同的内容。
这是接收部分。正如你所看到的,它把数据放在一个字节数组中,然后播放它。没有什么奇怪的。
byte[] byteData = udpClient.Receive(ref remoteEP);
waveProvider.AddSamples(byteData, 0, byteData.Length);
这是发送/录制部分。
private void Sendv2()
{
try
{
if (connect == true)
{
if (AudioDevice == "Wasapi Loopback")
{
SendStream.StopRecording();
Waloop.StartRecording();
}
else
{
Waloop.StopRecording();
SendStream.StartRecording();
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
void Sending(object sender, NAudio.Wave.WaveInEventArgs e)
{
if (connect == true && MuteMic.Checked == false)
{
udpClient.Send(e.Buffer, e.BytesRecorded, otherPartyIP.Address.ToString(), 1500);
}
}
void SendWaloop(object sender, NAudio.Wave.WaveInEventArgs e)
{
byte[] newArray16Bit = new byte[e.BytesRecorded / 2];
short two;
float value;
for (int i = 0, j = 0; i < e.BytesRecorded; i += 4, j += 2)
{
value = (BitConverter.ToSingle(e.Buffer, i));
two = (short)(value * short.MaxValue);
newArray16Bit[j] = (byte)(two & 0xFF);
newArray16Bit[j + 1] = (byte)((two >> 8) & 0xFF);
}
if (connect == true && MuteMic.Checked == false)
{
udpClient.Send(newArray16Bit, newArray16Bit.Length, otherPartyIP.Address.ToString(), 1500);
}
}
Waloop是一个环回,所以它通过另一个"通道",但它在这里并不重要。
非常简单,当数据可用时(当它正在录制时),如果连接为真等,它只会发送缓冲区。
很像接收器的部分,但另一方面。
现在我目前如何解决这个问题是这样的:
if (waveProvider.BufferedDuration.Milliseconds > 40)
{
waveProvider.ClearBuffer();
TimesBufferClear++;
}
因此,如果缓冲区超过40ms,我将清除缓冲区(这是在定时器中,间隔600ms)。(TimesBufferClear++;只是为了让我可以跟踪它被清除的时间)
不幸的是,我不知道如何防止缓冲区增加,将其设置为强制状态(20ms等)只会导致播放效果越来越差,因为缓冲区越高,它并没有真正停止,它只是忽略了我认为的以上部分。
这是输入设备的创建。在我的实现中,它与ASIO和Wasapi有点不同,但它的工作原理基本相同,唯一真正的区别是,正如你在代码中看到的那样,我告诉UI ASIO是打开还是关闭的,最后我将DataAvailable事件添加到SendStream(任何输入、麦克风等)和Waloop(正在播放的环回声音)中。
private void CheckAsio()
{
if (NAudio.Wave.AsioOut.isSupported())
{
Aut = new NAudio.Wave.AsioOut();
ASIO.Text += "'nSupported: " + Aut.DriverName;
ASIO.ForeColor = System.Drawing.Color.Green;
Aut.Init(waveProvider);
Aut.Play();
SendStream.NumberOfBuffers = 2;
SendStream.BufferMilliseconds = 10;
}
else
{
AsioSettings.Enabled = false;
ASIO.Text += "'n Not Supported: Wasapi used";
ASIO.ForeColor = System.Drawing.Color.DarkGray;
Wasout = new WasapiOut(AudioClientShareMode.Shared, 0);
Wasout.Init(waveProvider);
Wasout.Play();
SendStream.NumberOfBuffers = 2;
SendStream.BufferMilliseconds = 9;
}
SendStream.DataAvailable += Sending;
Waloop.DataAvailable += SendWaloop;
}
我甚至不确定这个问题是否能解决。但是,由于我没有看到其他语音聊天程序有它,我猜一定有一些事情可以做。
在大多数应用程序中,处理这种情况的方式似乎是以定义的速率(以样本/秒为单位)发送数据块,并丢弃超过该速率的数据块。如果发送方资源有限,无法保持速率,则流将具有音频间隙。当传输速率被锁定得高于网络连接所能处理的速度时,或者当CODEC代码占用太多时间时,这种情况经常发生在拨号音频呼叫中。
但从事物的声音来看,缓冲和跳跃是症状,而不是原因。问题的根源是您的流程被搁置以用于其他操作。您可以通过以更高的进程和/或线程优先级运行来解决此问题。优先级越高,中断的次数就越少,这将降低数据排队等待处理的可能性。
在.NET中,您可以非常简单地提高进程和/或线程的优先级。流程优先级:
using System.Diagnostics;
...
Process.GetCurrentProcess().PriorityClass = PriorityClass.Highest;
或者对于线程:
using System.Threading;
...
Thread.CurrentThread.Priority = ThreadPriority.Highest;
这不是一个完整的解决方案,因为在各种情况下,操作系统仍然会从应用程序中窃取时间片,但在具有充足内存的多CPU/内核系统中,您应该能够很好地获得稳定的录制环境。
当然,没有防傻的方法,总有一台速度慢的电脑会把你搞砸,所以你应该允许系统在必要时丢弃多余的样本。跟踪您发送的数据量以及何时开始备份,丢弃超过最大采样数/秒的任何数据。这样,你的服务器(或客户端)就不会缓冲越来越多的数据,也不会越来越落后于实时性。
其中一个选项是为您发送的每个数据包加上时间戳,以便客户端可以选择何时开始丢弃数据以进行追赶。与其越来越不同步,不如在这里和那里损失几毫秒的输出。