算法 - 使用外部编解码器/调制解调器处理抖动和漂移

时间:2014-09-13 21:43:00

标签: c multithreading audio clock

我正在用C编写一个小模块来处理全双工音频系统的抖动和漂移。它充当一个非常原始的语音聊天模块,连接到使用独立时钟的外部调制解调器,独立于我的主系统时钟(即:它不受系统主时钟限制)。

该来源基于在线提供的现有示例:http://svn.xiph.org/trunk/speex/libspeex/jitter.c

我有4个音频流:

  • 网络上行(我的声音,处理后,转到远端扬声器)
  • 网络下行链路(远端的语音,处理之前,来找我)
  • 扬声器输出(远端的声音,处理后,到本地扬声器)
  • 麦克风输入(我的声音,处理前,来自本地麦克风)

我有两个独立的执行线程。一个处理本地设备和缓冲区(即:将处理后的音频播放到扬声器,从麦克风捕获数据并将其传递到DSP处理库以消除背景噪声,回声等)。另一个线程处理拉动网络下行链路信号并将其传递到处理库,并从库中获取处理后的数据并通过上行链路连接推送它。

两个线程使用互斥锁和一组共享循环/环形缓冲区。我正在寻找一种方法来实现一个确定的(安全可靠的)抖动和漂移校正机制。通过抖动,我指的是一个具有可变占空比但与理想时钟频率相同的时钟。

Ideal clock signal and jittery signal

我需要纠正的另一个潜在问题是漂移,假设两个时钟都使用理想的50%占空比,但它们的基频偏差为±5%。

enter image description here

最后,这两个问题可以同时发生。什么是理想的方法?我目前的方法是使用一种抖动缓冲区。它们只是数据缓冲区,它实现移动平均值来计算它们的平均“填充”级别。如果一个线程试图从缓冲区读取,并且没有足够的数据可用并且存在缓冲器下溢,我只需通过提供备用的归零数据包或通过复制数据包即时生成数据(即:丢包隐藏)。如果数据进入太快,我会丢弃整个数据包,并继续前进。这会处理抖动部分。

问题的后半部分是漂移校正。这是平均填充水平指标有用的地方。对于所有缓冲区,我可以计算各种缓冲区中的相对增长/减少水平,并且每隔一段时间增加或减少少量样本,以便所有缓冲区水平都悬停在共同的平均“填充”水平附近。

这种方法是否有意义,是否有更好或“行业标准”的方法来处理这个问题?

谢谢。

参考


  1. 字时钟 - 抖动与频率漂移有什么区别?,访问2014-09-13,<http://www.apogeedigital.com/knowledgebase/fundamentals-of-digital-audio/word-clock-whats-the-difference-between-jitter-and-frequency-stability/>
  2. Jitter.c ,访问2014-09-13,<http://svn.xiph.org/trunk/speex/libspeex/jitter.c>

2 个答案:

答案 0 :(得分:2)

我很想知道这是否可行。我会扔掉我的两位。

我是一名新手程序员,但学习过音频工程/互动音频。

我的第一个假设是这是不可能的。至少不是基于样本到样本。尤其不适用于复杂的音频数据和人类语音等波形。该程序可能没有预期波形“应该”的样子。

这就是具有温度控制内部时钟的高端音频接口的原因。

另一方面,也许有一个库可以检测出抖动的症状,不知何故......    在这种情况下,我会很好奇地听到它。

就漂移校正而言,我可能不了解编程方面的内容,但是你不应该以特定的采样率拉动音频吗?我相信采样率/漂移是在硬件级别处理的。

我真的希望这会有所帮助。你可能不得不让我离家更近。

答案 1 :(得分:2)

我遇到了一个类似但虽然简单的问题。我无法完全回答你的问题,但我希望分享我遇到的一些实际问题的解决方案,无论如何都会让你受益。

去年我正在开发一个系统,它应该同时记录和呈现多个音频设备,每个音频设备可能会勾选不同的时钟。最明显的例子是2个设备上的双工流,但它也只处理多个输入/输出。总而言之,比你的情况(单线程和没有网络i / o)简单一点。最后,我不相信处理2个以上的设备比2更难,任何有多个时钟的系统都必须处理同样的问题。

我学到了一些东西:

  • 选择一个流并将其指定为&#34;真相&#34; (即,将所有其他流同步到公共主时钟)。如果你不这样做,你就不会有一个明确定义的当前样本位置&#34;的概念,没有它就没有什么可以同步的。这样做的好处是系统中至少有一个流始终是干净的(没有掉落/填充样本)。
  • 使用额外缓冲区处理抖动的方法是正确的。没有它,即使在具有相同标称采样率的流上,您也会不断地丢弃/填充。
  • 考虑一下你是否想为&#34; master&#34;引入这样的抖动缓冲区。流也。这样做意味着在主流中引入人为延迟,而不这样做意味着其余的流将落后。
  • 我不确定丢弃整个数据包是否是个好主意。为什么不尝试尽可能多地使用样品呢?特别是对于大的数据包大小,这是不太明显的。
  • 详细说明以上情况,我被以下情况严重咬伤:假设s1(主站)每秒产生48000帧,s2每2秒产生96000帧。第1轮:从s1读取48000,从s2读取0。第2轮:从s1读取48000,从s2读取96000 - >溢出。丢弃整个数据包。第3轮:从s1读取48000,从s2读取0。等等。显然这是一个人为的例子,但我遇到的情况是,平均而言,我使用这种方案丢弃了50%的二级流数据。引入抖动缓冲区确实有帮助,但并没有完全解决这个问题。请注意,这与时钟抖动/时滞并不严格相关,只是有些驱动程序会定期更新其填充值,并且无法准确地向您报告硬件缓冲区中的实际内容。
  • 当您确实遇到时钟抖动时会发生此问题的另一个变体,但您选择的API并不能让您控制数据包大小(例如,允许您请求的帧数少于实际可用的帧数)。假设s1(主)记录@ 1000 Hz和s2每秒交替@ 1000和1001hz。第1轮,从两者读取1000帧。第2轮,从s1读取1000帧,从s2读取1001 - &gt;溢出。等等,平均而言,你会在s2上转储大约50%的帧。请注意,如果您的API允许您说“即使我知道您已经获得更多&#34;”也会给我1000个样本,这不是一个问题。通过这样做,您最终会溢出硬件输入缓冲区。
  • 为了最大限度地控制何时丢弃/填充,我发现最简单的方法就是保持输入缓冲区为空并且输出缓冲区已满。这样所有丢弃/填充都发生在抖动缓冲区中,您至少知道并控制发生的事情。
  • 如果可能的话,尝试分离您的程序逻辑:困难的部分是找出填充/删除样本的位置。一旦你掌握了这一点,就可以轻松尝试不同的焊盘/跌落,采样保持,插值等变化。

总而言之,我说你的解决方案看起来很合理,虽然我不确定&#34;丢弃整个数据包的事情&#34;我绝对会选择一个流作为主人来同步。为了完整性,我最终提出了解决方案:

  • 1假设每个流上有一个J大小的抖动缓冲区。
  • 2:等待大小为M的数据包在主数据流上可用(M通常来自流延迟)。我们将向应用程序提供M帧输入/输出。我没有在主流上实现额外的缓冲区。
  • 3:对于所有输入流:令H为硬件缓冲区中记录的帧数,B为当前在抖动缓冲区中记录的帧数,A为应用程序可用的帧数:A等于H + B.
  • 3a:如果A&lt; M,我们有输入下溢。向应用提供录制的帧+(M - A)填充帧。由于设备可能很慢,请用静音填充1/2的抖动缓冲区。
  • 3b:如果A == M,请向应用提供A帧。抖动缓冲区现在为空。
  • 3c:如果A> M但是(A-M)&lt; = J,向应用程序提供M个记录的帧。 A-M帧保留在抖动缓冲区中。
  • 3d:如果A&gt; M和(A-M)> J,我们输入溢出。向应用程序提供M个记录帧,其余帧将J / 2放回抖动缓冲区,我们使用M + J / 2帧,并将A - (M + J / 2)帧丢弃为溢出。不要试图保持抖动缓冲区满,因为设备可能很快,我们不希望在下一轮再次溢出。
  • 4:3的倒数排序:对于输出,快速设备将下溢,慢速设备将溢出 A,H和B是相同的东西,但这次它们不代表可用的帧但可用的填充(例如,我可以向应用程序提供多少帧来写入)。 尝试不惜一切代价保持硬件缓冲区满。

这个方案对我来说非常好,虽然还有一些事情需要考虑:

  • 涉及大量的簿记。确保对于输入缓冲区,数据始终从硬件 - >抖动缓冲区 - >应用程序流出,并始终从应用程序 - >抖动缓冲区 - >硬件流出。很容易犯错误,认为你可以跳过&#34;跳过&#34;抖动缓冲区中的帧,如果有足够的样本可以从硬件直接连接到应用程序。这基本上会弄乱音频流中帧的时间顺序。
  • 此方案在辅助流上引入了可变延迟,因为我试图尽可能地推迟填充/删除的时刻。这可能是也可能不是问题。我发现在实践中推迟这些操作会产生更好的结果,这可能是因为许多&#34; minor&#34;只有少数样本的故障比偶尔发生的更大的打嗝更令人讨厌。

此外,PortAudio(开源音频项目)实施了类似的方案,请参阅http://www.portaudio.com/docs/proposals/001-UnderflowOverflowHandling.html。浏览邮件列表并查看有哪些问题/解决方案可能是值得的。

请注意,到目前为止我所说的一切只是与音频硬件的互动,我不知道这是否能够与网络流同样有效,但我看不出任何明显的为什么不呢。只需选择1个音频流作为主设备,然后将另一个音频流同步到它,并对网络流执行相同操作。这样,您最终会得到两个或多或少的独立系统,这些系统仅由环形缓冲器连接,每个系统都有一个内部一致的时钟,每个时钟都运行在它自己的线程上。如果您的目标是降低音频延迟,那么您还需要删除互斥锁并选择某种类型的无锁FIFO。