混合声音的算法

68

我有两个原始音频流需要相加。为了这个问题,我们可以假设它们具有相同的比特率和位深度(例如16位样本,44.1kHz采样率)。

显然,如果我只是把它们加在一起,那么我会溢出和下溢我的16位空间。如果我把它们加在一起并除以二,那么每个音频的音量都会减半,这在声学上不正确-如果两个人在房间里说话,他们的声音不会变得安静一半,并且麦克风可以捕捉到他们而没有触发限制器。

  • 那么,在我的软件混音器中将这些声音相加的正确方法是什么?
  • 我错了吗?正确的方法是将每个音频的音量减半吗?
  • 我需要添加压缩器/限制器或其他处理阶段才能获得我所追求的音量和混音效果吗?

-Adam


7
同一个问题,更好的答案:http://dsp.stackexchange.com/questions/3581/algorithms-to-mix-audio-signals-without-clipping - sethcall
我真的很失望。在现实生活中,我总是听到两个信号无论它们处于什么相位。但是简单地将两个相位反转的波的样本相加将导致完全的寂静。没有任何提及... - Alba Mendez
4
相位抵消是真实存在的。将两个扬声器放在一起,从其中一个反转相位(交换电线),你的低音会被破坏。之所以没有完全的抵消是因为你的扬声器不是点源,并且你有两只耳朵。 - Roddy
我知道,我知道...但是,当人们听到“声音混合”时,他们并不希望两种声音在相位上互相抵消,导致静音。 - Alba Mendez
我不希望两个乐器的频率被“幸运地”相互抵消而产生相位反转。 - Alba Mendez
你绝对可以找到方法来防止相位抵消,但这样做的风险是创造出一个不真实的混音。相位抵消是音频物理的自然产物,在真实的模拟世界中经常发生。我记得有一个本地乐队发行了一张黑胶CD,其中有一首“隐藏”曲目,需要你将左右扬声器对准彼此以取消主旋律并揭示隐藏的曲目。这是一个很有趣的听觉效果。 - undefined
20个回答

33

你应该将它们相加,但要将结果剪裁到允许的范围内以防止溢出/下溢。

如果发生剪切,你引入音频失真,但这是不可避免的。你可以使用剪切代码来"检测"这种情况,并向用户/操作员报告它(相当于混音器上的红色'clip'灯...)

你可以实现一个更"正式"的压缩机/限幅器,但在不知道你确切应用的情况下,很难说是否值得这样做。

如果你要进行大量音频处理,你可能希望将音频级别表示为浮点值,并仅在处理结束时返回到16位空间。高端数字音频系统通常采用这种方式。


1
这个答案是正确的,但我会在下面加上一些关于如何实现自动级别控制的注释(在我还没有评论权限之前写的)。 - podperson
8
@Kyberias,这没有意义;第一句话字面上就解释了要做什么。 - user1881400
OP已经知道这个答案所建议的,并且也知道这样做的缺点,来自于问题“显然,如果我只是把它们加在一起,我会溢出和下溢我的16位空间。”@user1881400 - Ratul Sharker

31

我更喜欢评论其中排名较高的两个答案之一,但由于我的声望有限(我猜测),我无法发表评论。

“打勾”答案:添加并削减是正确的,但如果您想避免削减,则不是。

链接开始的回答针对[0,1]中的两个正信号提供了可行的巫术算法,但随后应用了一些非常错误的代数方法,推导出了一个完全不正确的算法,适用于带符号值和8位值。该算法也无法扩展到三个或更多输入(信号的乘积将降低,而总和将增加)。

因此-将输入信号转换为float,将它们缩放到[0,1]范围内(例如,有符号16位值将变为
float v = (s + 32767.0) / 65536.0(足够接近...))
然后相加。

要缩放输入信号,您可能需要进行一些实际工作,而不仅仅是乘以或减去巫术值。我建议保持运行平均音量,然后如果它开始偏高(超过0.25)或偏低(低于0.01),则开始应用基于音量的缩放值。这本质上成为自动级别实现,并且可以扩展到任意数量的输入。最重要的是,在大多数情况下,它不会对您的信号造成任何影响。


谢谢你的笔记!我认为这值得回答,但是你现在已经有50个声望了,所以你应该能够在网站上发表评论了。 - Adam Davis

25

这里有一篇关于混音的文章,链接。我很想知道其他人对此的看法。


1
很有趣。基本上它执行加法,然后对信号进行非常简单的“压缩”,以避免削波。问题是,即使没有削波的需要,这也会显着改变样本值。对于某些应用(例如电话、游戏),这种方法可能效果不错。但对于高端音频处理来说,这可能被认为是降低信号质量... - Roddy
9
这篇文章存在误导(请参考我下面的回答)。如果您将示例值输入到他的最终公式中,将会得到糟糕的输出结果(他的代数运算有误)。例如,将沉默输入值带入公式将给出-1的输出。无论如何,它不能扩展到超过两个输入,并且这是一种没有实际基础的巫术算法。 - podperson
更改每个样本的音量是不明智的。另外,该算法不正确,因为如果您有两个具有相同信号的通道,则这两个通道的混合应与每个单通道相同。但是该算法导致信号丢失。 - SuperLucky
1
那篇文章是完全错误的,正如许多人所指出的那样。请停止点赞,你只会误导他人。 - Bill Kotsias

20

大多数音频混合应用程序会使用浮点数进行混合(32位对于混合少量流已经足够好了)。将16位采样转换为浮点数,范围为-1.0到1.0,表示16位世界中的全幅。然后将采样相加——现在你有充足的余地。最后,如果你得到任何超过全幅值的采样,可以将整个信号衰减或使用硬限制(将值剪裁为1.0)。

这比将16位采样相加并让它们溢出要产生更好的声音效果。以下是一个非常简单的代码示例,演示如何将两个16位采样相加:

short sample1 = ...;
short sample2 = ...;
float samplef1 = sample1 / 32768.0f;
float samplef2 = sample2 / 32768.0f;
float mixed = samplef1 + sample2f;
// reduce the volume a bit:
mixed *= 0.8;
// hard clipping
if (mixed > 1.0f) mixed = 1.0f;
if (mixed < -1.0f) mixed = -1.0f;
short outputSample = (short)(mixed * 32768.0f)

1
当然可以,但这会增加剪辑的可能性,因此请相应地调整音量。 - Mark Heath
这对你引入了白噪声吗,@MarkHeath? - Jeremy
通过将混合物乘以0.8,您不仅可以将噪音水平接近“平均”吗?如果您将负值(例如-0.5)乘以0.8,则会更接近0,换句话说,它会变得更高...因此,您需要在乘法之前将其转换为0+范围,或者“稍微降低音量”的评论就不准确。 - Bram Vaessen

10

"减少一半的噪音"这个说法不完全正确。由于耳朵的对数响应,将样本分成一半会使其静音6分贝 - 肯定是可以察觉到的,但不是灾难性的。

你可能想通过乘以0.75来妥协。这将使它变得更安静3分贝,但会降低溢出的几率,并在发生时减少失真。


3 dB的降噪相当于功率减半,因此把采样值除以sqrt(2)。这意味着要乘以0.707 (1/sqrt(2)) 而不是0.75。尽管如此,我同意用位移实现乘以0.75更为简单。 - Gauthier
@Gauthier,我只是说得不太准确。 - Mark Ransom
1
@JorisWeimar,他说减半功率需要除以根号2是完全正确的。按照惯例,我们称之为-3db,尽管技术上应该是-3.0103db。再次强调,这只是近似值。 - Mark Ransom
1
但是 @JorisWeimar,这与sqrt(2)有一切关系!-3db的数值是对sqrt(2)的近似,而不是反过来。功率与电压的平方成正比,所以将功率减半需要将电压(信号)降低sqrt(2)倍。这与2^10 (1024)非常接近于10^3 (1000)的原因完全是巧合。 - Mark Ransom
1
@JorisWeimar db是一种比率的测量单位,在dbfs的情况下它表示信号的满幅幅度与被测信号的比值。如果你考虑到这个比率是乘数因子,那么你的公式就是完全正确的。这就是我得出上述数字的方法:20 * log(1/sqrt(2)) = -3.0103 - Mark Ransom
显示剩余3条评论

8
我不敢相信没有人知道正确的答案。每个人都很接近,但仍然是纯哲学。 最接近的,即最好的是: (s1 + s2)-(s1 * s2)。 这是一个很好的方法,特别适用于微控制器。
因此,算法如下:
1. 找出您想要输出声音的音量范围。可以是其中一个信号的平均值或最大值。 factor = average(s1) 您假设两个信号已经OK,未溢出32767.0。
2. 使用此系数对两个信号进行归一化: s1 = (s1 / max(s1)) * factor s2 = (s2 / max(s2)) * factor 3. 将它们加在一起,并使用相同的因子将结果标准化: output = ((s1 + s2) / max(s1 + s2)) * factor 请注意,在步骤1之后,您实际上不需要返回整数,您可以在-1.0到1.0的区间内使用浮点数,并在最后使用之前选择的幂因子将其应用于整数。我希望现在没有犯错,因为我很匆忙。

这是错误的。例如,假设s1和s2都为0.5,则s1 + s2 => 1,max(s1,s2)为0.5,因此输出为2。您已经超出了剪辑范围,简单地相加是不可以的。另外,0.25和0.25产生相同的结果。 - podperson

7

您还可以使用像y= 1.1x - 0.2x^3这样的算法来为曲线预留一些余地,并在顶部和底部设置限制。当玩家同时演奏多个音符(最多6个)时,我在Hexaphone中使用了这种算法。

float waveshape_distort( float in ) {
  if(in <= -1.25f) {
    return -0.984375;
  } else if(in >= 1.25f) {
    return 0.984375;
  } else {    
    return 1.1f * in - 0.2f * in * in * in;
  }
}

它不是百分之百可靠的 - 但可以使你达到1.25级,并将剪辑平滑成一个漂亮的曲线。会产生谐波失真,听起来比剪辑更好,并且在某些情况下可能是可取的。


尝试过这个方法,效果很好。是一个不错的快速解决方案来处理剪辑问题。 - Ehz
此答案所隐含的意思是在混合之前应将其转换为浮点数。 - Ehz
这看起来很有趣。你从哪里得到那些神奇的常数?(特别是1.25和0.984375?) - Cameron
1
1.25是我愿意接受的上限(125%水平)。在我指定的公式中,当x=1.25时,y值为0.984375。 - Glenn Barnett
4
记录一下:这是压缩(以及少量的扩展)。 - Gauthier

3

将样本转换为浮点值,范围从-1.0到+1.0,然后:

out = (s1 + s2) - (s1 * s2);

2
我想我得自己琢磨一下了。这似乎是合适的,但如果输入是1和-1,则结果为1。不确定是否要为此使用拉普拉斯变换,但如果您有任何关于为什么或如何工作的更多信息或参考资料,我将不胜感激。 - Adam Davis
2
请注意,该文章指出输入值应在0到1之间。 - Gauthier

3
如果你想做得正确,我建议至少从理论上看一下开源软件混音器实现。
一些链接: Audacity GStreamer 实际上,你应该使用一个库。

1
Audacity只会添加样本,如果样本很高,则会产生剪辑。您必须手动调整每个轨道的增益以防止剪辑。 - olafure

3

你关于将它们加在一起的想法是正确的。你可以扫描这两个文件的总和以查找峰值点,如果它们达到某种阈值(或者如果它及其周围的点的平均值达到阈值),则可以将整个文件缩小。


我同意你的观点,但对于声音流来说并不实用,因为你无法窥视声音,也许一个窗口化的动态增益调整会更好? - SuperLucky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接