语音聊天,延迟增加(缓冲) - 可以解决吗?

时间:2013-08-13 16:42:28

标签: c# buffer naudio

好的,我正在制作语音聊天软件。 我正在使用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,所以我不必一直重写它。 如果我在缓冲区上设置了某个长度,例如20ms,则使用DiscardOnBufferOverflow。它将丢弃上面的任何内容,否则它将返回异常。但是我认为如果我没有设定长度它就没有做任何事情,默认它可能是无限的。

除此之外,SendStream是一个WaveInEvent,这意味着当我使用DataAvailable时它将在Ba​​ckgroundThread上运行。除了它是一个环回之外,Waloop几乎是一样的。 waveprovider用于接收部分以播放音频。 Waveformat是一种波形格式,它可以用来设置它,并且至少在我的应用程序中具有相同的功能。

这是接收部分。你可以,它将数据放在一个字节数组中,然后播放它。没什么奇怪的。

   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是一个Loopback,所以它通过另一个“频道”,但在这里并不重要。

非常简单,当数据可用时(录制时),如果连接为真等,它只会发送缓冲区。

非常像接收器部分,但反过来。

现在我如何解决这个问题是这样的:

 if (waveProvider.BufferedDuration.Milliseconds > 40)
            {

                waveProvider.ClearBuffer();
                TimesBufferClear++;
            }

所以我清除缓冲区,如果超过40ms(这是在600毫秒间隔的计时器)。 (TimesBufferClear ++;只是这样我可以跟踪它被清除的时间)

现在可悲的是,我不知道如何防止缓冲区增加,并将其设置为强制状态(20ms等)只会导致播放更糟,更糟糕的是缓冲区越高,因为它没有真的停下来,它只是忽略了我认为的那部分。

这是输入设备的创建。它在我的实现中与ASIO和Wasapi有点不同,但它几乎是一样的,只有真正的区别在于我告诉UI ASIO打开或关闭,你可以在代码中看到,最后我添加了DataAvailable事件发送到SendStream(任何输入,Microphone等)和Waloop(正在播放的Loopback声音)。

  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;

    }

我不确定这是否可以解决。但是,由于我没有看到其他语音聊天程序,我猜我必须有一些可以做的事情。

1 个答案:

答案 0 :(得分:1)

在大多数应用程序中处理这种方式的方法是以定义的速率(以采样/秒为单位)发送数据块,并丢弃超过该速率的数据块。如果发送方受资源限制且无法维持费率,则流将具有音频间隙。当传输速率被锁定的速度高于网络连接可以处理的速度,或者当CODEC代码占用太多时间时,这通常会通过拨号进行音频呼叫。

但是从事物的声音来看,缓冲和跳跃是症状,而不是原因。问题的根源在于您的流程因其他操作而被搁置。您可以通过运行更高的进程和/或线程优先级来解决此问题。优先级越高,中断的次数越少,这将降低数据排队处理的可能性。

在.NET中,您可以相当简单地提高您的流程和/或线程优先级。对于流程优先级:

using System.Diagnostics;

...

Process.GetCurrentProcess().PriorityClass = PriorityClass.Highest;

或者是一个主题:

using System.Threading;

...

Thread.CurrentThread.Priority = ThreadPriority.Highest;

这不是一个完整的解决方案,因为操作系统仍会在各种情况下从您的应用程序中窃取时间片,但在具有足够内存的多CPU /核心系统中,您应该在稳定的录制中获得相当好的镜头环境。

当然没有万无一失的方法,并且总会有一台慢速计算机会弄乱你,所以你应该允许系统在必要时丢弃多余的样本。跟踪您发送的数据量以及何时开始备份,丢弃超过最大样本数/秒的数据。这样你的服务器(或客户端)就不会缓冲越来越多的数据,而是越来越落后于实时数据。

有一个选项可以为您发送的每个数据包加上时间戳,以便客户端可以选择何时开始丢弃数据以赶上。最好在这里和那里丢失几毫秒的输出,而不是漂移越来越不同步。