【问题标题】:Mixing 16 bit linear PCM streams and avoiding clipping/overflow混合 16 位线性 PCM 流并避免削波/溢出
【发布时间】:2012-08-18 20:28:04
【问题描述】:

我试图将 2 个 16 位线性 PCM 音频流混合在一起,但我似乎无法克服噪音问题。我认为它们在将样本混合在一起时来自溢出。

我有以下功能...

short int mix_sample(short int sample1, short int sample2)
{
    return #mixing_algorithm#;
}

...这是我尝试过的#mixing_algorithm#

sample1/2 + sample2/2
2*(sample1 + sample2) - 2*(sample1*sample2) - 65535
(sample1 + sample2) - sample1*sample2
(sample1 + sample2) - sample1*sample2 - 65535
(sample1 + sample2) - ((sample1*sample2) >> 0x10) // same as divide by 65535

其中一些产生了比其他更好的结果,但即使是最好的结果也包含相当多的噪音。

有什么办法解决吗?

【问题讨论】:

  • 你能写出完整的算法吗,我看不到任何作业!!
  • 当你将sample1和sample2除以2时,你得到的误差范围是1。

标签: c audio signal-processing pcm mixing


【解决方案1】:

这里有一个讨论:https://dsp.stackexchange.com/questions/3581/algorithms-to-mix-audio-signals-without-clipping 关于为什么 A+B - A*B 解决方案不理想。隐藏在本次讨论的其中一个 cmets 中的建议是对这些值求和并除以信号数量的平方根。额外的剪裁检查也无妨。这似乎是一个合理(简单而快速)的中间立场。

【讨论】:

    【解决方案2】:

    这是我在最近的合成器项目中所做的。

    int* unfiltered = (int *)malloc(lengthOfLongPcmInShorts*4);
    int i;
    for(i = 0; i < lengthOfShortPcmInShorts; i++){
        unfiltered[i] = shortPcm[i] + longPcm[i];
    }
    for(; i < lengthOfLongPcmInShorts; i++){
         unfiltered[i] = longPcm[i];
    }
    
    int max = 0;
    for(int i = 0; i < lengthOfLongPcmInShorts; i++){
       int val = unfiltered[i];
       if(abs(val) > max)
          max = val;
    }
    
    short int *newPcm = (short int *)malloc(lengthOfLongPcmInShorts*2);
    for(int i = 0; i < lengthOfLongPcmInShorts; i++){
       newPcm[i] = (unfilted[i]/max) * MAX_SHRT;
    }
    

    我将所有 PCM 数据添加到一个整数数组中,这样我就可以得到所有未过滤的数据。

    之后,我在整数数组中寻找绝对最大值。

    最后,我将整数数组放入一个短整数数组中,方法是将每个元素除以该最大值,然后乘以最大短整数值。

    通过这种方式,您可以获得适合数据所需的最小“空间”。

    您也许可以对整数数组进行一些统计并整合一些剪辑,但对于我需要的最小空间量对我来说已经足够了。

    【讨论】:

      【解决方案3】:

      我找到的最佳解决方案是given by Viktor Toth。他为 8 位无符号 PCM 提供了一个解决方案,并将其更改为 16 位有符号 PCM,会产生这样的结果:

      int a = 111; // first sample (-32768..32767)
      int b = 222; // second sample
      int m; // mixed result will go here
      
      // Make both samples unsigned (0..65535)
      a += 32768;
      b += 32768;
      
      // Pick the equation
      if ((a < 32768) || (b < 32768)) {
          // Viktor's first equation when both sources are "quiet"
          // (i.e. less than middle of the dynamic range)
          m = a * b / 32768;
      } else {
          // Viktor's second equation when one or both sources are loud
          m = 2 * (a + b) - (a * b) / 32768 - 65536;
      }
      
      // Output is unsigned (0..65536) so convert back to signed (-32768..32767)
      if (m == 65536) m = 65535;
      m -= 32768;
      

      使用这种算法意味着几乎不需要裁剪输出,因为它只有一个值在范围内。与直接平均不同,即使另一个源静音,一个源的音量也不会降低。

      【讨论】:

      • “安静”是什么意思? - 这通常是指低幅度靠近中间),但在这里你似乎是指(低于中间),而当一个或两个都是正数时执行“响亮”方程(在移位之前 - 即添加直流偏置)。除此之外,volume 是对 信号 的感知,而不是单个样本 - “响亮”的声音将在整个范围内包含样本。
      • @Clifford:中间是可用范围的中间,所以如果值在 0 和 65535 之间,那么中间是 32767。最好在 Viktor Toth 页面的链接中解释。跨度>
      • 我意识到 - 我的问题是修辞性的 - 在这种情况下,“安静”和“大声”这两个词是不准确和误导性的。
      • 这正是我将“安静”放在吓人引号中的原因,以暗示其含义与您可能期望的有所不同:-)然后我随后解释了我的意思...
      • 原来的解释是与中点的关系; “安静”一词在此处的使用方式不同且正确,意思是“接近中点”。尽管这是 IMO 的最佳答案(因此获得了赞成票),但 cmets 是对 Victor Toth 解释的歪曲。
      【解决方案4】:

      由于您处于时域中,因此频率信息在连续样本之间的差异中,当您除以 2 时,您会损坏该信息。这就是为什么添加和剪辑效果更好的原因。削波当然会添加非常高频的噪声,可能会被过滤掉。

      【讨论】:

      • 我预计 OP 听到的噪音是由值换行引起的,而不是像丢失分辨率那样细微的任何东西
      【解决方案5】:

      这是一个描述性的实现:

      short int mix_sample(short int sample1, short int sample2) {
          const int32_t result(static_cast<int32_t>(sample1) + static_cast<int32_t>(sample2));
          typedef std::numeric_limits<short int> Range;
          if (Range::max() < result)
              return Range::max();
          else if (Range::min() > result)
              return Range::min();
          else
              return result;
      }
      

      要混合,只需添加和剪辑!

      为避免削波失真,您需要使用饱和度或限制器。理想情况下,您将有一个小的int32_t 缓冲区和少量的前瞻。这会引入延迟。

      比到处限制更常见的是在信号中留出一些“余量”。

      【讨论】:

      • 避免剪裁的唯一“正确”方法是除以二。在“失真和噪声”部分中有一些说明性代码:blog.bjornroche.com/2013/05/…
      • 不得不对此投反对票,因为它只解决了混合单个样本的“本地”问题。如果你看一个大声波,这实际上是一个可怕的算法,因为它会切断高振幅的波并引入削波噪声。一种正确的方法是使用浮动样本并平滑地应用动态波幅压缩。这将确保不会发生人为削波 - 在高振幅期间声音只会变得更安静。
      • @JormaRebane 您是否系统地否决初学者对每个主题的问题的回答?
      • 当一个信号静默时,除以二的方法将输出音量减半。可能不是人们想要的。
      【解决方案6】:

      我认为它们应该是映射[MIN_SHORT, MAX_SHORT] -&gt; [MIN_SHORT, MAX_SHORT] 的函数,但它们显然不是(除了第一个),因此会发生溢出。

      如果unwind的提议行不通你也可以试试:

      ((long int)(sample1) + sample2) / 2
      

      【讨论】:

      • 同时添加信号是正确的;通过简单的规范化来维持范围,一个信号会影响另一个信号。例如,如果sample1 始终为零(静默),您可能只需要 sample2,但您会得到sample2 / 2 - 即输出更安静。
      • 是的,你完全正确。但是解决了溢出和削波的问题。恕我直言,最好的解决方案是根据信号的值缩放信号,例如w(s1,s2)*s1 + (1-w(s1,s2))*s2,其中w(s1,s2) 是一些函数,其中w(s1,0) = 1w(0,s2) = 00 &lt; w(s1,s2) &lt; 1s1 != 0 &amp;&amp; s2 != 0
      猜你喜欢
      • 2012-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      • 2014-08-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多