首页 文章

如何有效地确定用于循环的预渲染正弦波音频缓冲区的最小必要大小?

提问于
浏览
2

我编写了一个程序,以用户指定的频率生成正弦波,并在96kHz音频通道上播放 . 为了节省几个CPU周期,我采用了将一小段音频预渲染到缓冲区中的旧技巧,然后在循环中回放缓冲区,这样我就可以避免每秒调用sin()函数96000次程序的持续时间,而只是简单的内存复制 .

我的问题是有效地确定这个预渲染缓冲区的最小可用大小 . 对于某些频率来说很容易 - 例如,8kHz正弦波可以通过生成12个样本缓冲区并在循环中播放来完美表示,因为(8000 * 12 == 96000) . 然而,对于其他频率,正弦波的单个周期需要非整数个样本来表示,因此循环单个周期的样本将导致不可接受的毛刺 .

然而,对于这些频率中的一些,可以通过预渲染正弦波的多个周期并循环来解决该问题 - 如果我可以计算出需要多少个周期以便存在的周期数缓冲区将是整数,同时还保证缓冲区中的样本数是完整的 . 例如,12.8kHz的正弦波频率转换为7.5个样本的单周期缓冲区大小,不会干净地循环,但如果我将正弦波的两个连续周期渲染到15个样本缓冲区中,那么我可以干净地循环结果 .

我目前解决这个问题的方法是强力:我尝试所有可能的循环计数,看看是否有任何循环计数导致缓冲区大小,其中包含整数个样本 . 我认为这种方法不能令人满意,原因如下:

1)效率很低 . 例如,下面显示的程序(在0Hz和48kHz之间打印480,000个可能的频率值的缓冲区大小结果)在我的2.7GHz机器上完成需要35分钟 . 我认为必须有一个更快的方法来做到这一点 .

2)由于浮点错误,我怀疑结果不是100%准确 .

3)如果找不到小于10秒的可接受缓冲区大小,算法就会放弃 . (我可以使限制更高,但当然这会使算法更慢) .

那么,有没有办法分析地计算最小可用缓冲区大小,最好是在O(1)时间内?看起来应该很容易,但我还没弄清楚我应该使用什么样的数学 .

提前感谢您的任何建议!

#include <stdio.h>
#include <math.h>

static const long long SAMPLES_PER_SECOND              = 96000;
static const long long MAX_ALLOWED_BUFFER_SIZE_SAMPLES = (SAMPLES_PER_SECOND * 10);

// Returns the length of the pre-render buffer needed to properly
// loop a sine wave at the given frequence, or -1 on failure.
static int GetNumCyclesNeededForPreRenderedBuffer(float freqHz)
{
   double oneCycleLengthSamples = SAMPLES_PER_SECOND/freqHz;

   for (int count=1; (count*oneCycleLengthSamples) < MAX_ALLOWED_BUFFER_SIZE_SAMPLES; count++)
   {
      double remainder = fmod(oneCycleLengthSamples*count, 1.0);
      if (remainder > 0.5) remainder = 1.0-remainder;
      if (remainder <= 0.0) return count;
   }
   return -1;
}

int main(int, char **)
{
   for (int i=0; i<48000*10; i++)
   {
      double freqHz = ((double)i)/10.0f;
      int numCyclesNeeded = GetNumCyclesNeededForPreRenderedBuffer(freqHz);
      if (numCyclesNeeded >= 0)
      {
         double oneCycleLengthSamples = SAMPLES_PER_SECOND/freqHz;
         printf("For %.1fHz, use a pre-render-buffer size of %f samples (%i cycles, %f samples/cycle)\n", freqHz, (numCyclesNeeded*oneCycleLengthSamples), numCyclesNeeded, oneCycleLengthSamples);
      }
      else printf("For %.1fHz, there was no suitable pre-render-buffer size under the allowed limit!\n", freqHz);
   }
   return 0;
}

1 回答

  • 1
    number_of_cycles/size_of_buffer = frequency/samples_per_second
    

    这意味着如果您可以简化频率/ samples_per_second分数,则可以找到缓冲区的大小和缓冲区中的循环数 . 如果frequency和samples_per_second是整数,则可以通过查找最大公约数来简化分数,否则可以使用连续分数的方法 .

    例:

    假设你的频率是1234.5,你的samples_per_second是96000.我们可以乘以10将它们变成两个整数,所以我们得到比率:

    frequency / samples_per_second = 12345/960000

    最大公约数是15,因此它可以减少到823/64000 .

    因此,您需要在64000个采样缓冲区中进行823个周期才能准确再现频率 .

相关问题