混合声音的算法

时间:2008-12-17 21:08:20

标签: algorithm audio

我需要将两个原始声音流添加到一起。出于这个问题的目的,我们可以假设它们具有相同的比特率和比特深度(例如16比特样本,44.1khz采样率)。

显然,如果我只是将它们加在一起,我将溢出并下溢我的16位空间。如果我将它们加在一起并除以2,那么每个的体积减半,这是不正确的声音 - 如果两个人在一个房间里说话,他们的声音不会变得安静一半,而麦克风可以选择它们两者都没有达到限制器。

  • 那么在我的软件混音器中将这些声音添加到一起的正确方法是什么?
  • 我错了,正确的方法是将每一个的体积减少一半?
  • 我是否需要添加压缩器/限制器或其他处理阶段以获得我正在尝试的音量和混音效果?

- 亚当

20 个答案:

答案 0 :(得分:30)

您应该将它们一起添加,但将结果剪切到允许范围以防止上溢/下溢。

如果发生剪辑,在音频中引入失真,但这是不可避免的。您可以使用剪辑代码“检测”这种情况并将其报告给用户/操作员(相当于调音台上的红色'剪辑'灯......)

你可以实现一个更“适当”的压缩器/限制器,但不知道你的确切应用,很难说它是否值得。

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

答案 1 :(得分:27)

我更愿意对两个排名很高的回复中的一个做出评论,但由于我的声誉微薄(我猜)我不能。

“勾选”答案:加在一起并且剪辑是正确的,但如果你想避免剪辑则不是。

链接的答案以[0,1]中两个正信号的可行巫术算法开始,然后应用一些非常错误的代数来导出一个完全不正确的有符号值和8位值算法。该算法也不能扩展到三个或更多输入(信号的乘积会随着总和的增加而下降)。

所以 - 将输入信号转换为浮点数,将它们缩放到[0,1](例如,有符号的16位值将变为     
float v = ( s + 32767.0 ) / 65536.0 (close enough...))
然后总结它们。

要缩放输入信号,您应该做一些实际工作,而不是乘以或减去巫毒值。我建议保持一个运行平均音量,然后如果它开始漂移高(高于0.25说)或低(低于0.01说)开始应用基于音量的缩放值。这基本上成为一个自动级别的实现,它可以随任意数量的输入进行扩展。最重要的是,在大多数情况下,它根本不会弄乱您的信号。

答案 2 :(得分:27)

有一篇关于混合here的文章。我有兴趣知道其他人对此的看法。

答案 3 :(得分:17)

大多数音频混合应用程序将使用浮点数进行混合(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)

答案 4 :(得分:9)

“安静一半”不太正确。由于耳朵的对数响应,将样本分成两半会使6-db更安静 - 当然明显,但不是灾难性的。

您可能希望通过乘以0.75来妥协。这将使3-db更安静,但会减少溢出的可能性,并在发生失败时减少失真。

答案 5 :(得分:8)

我无法相信没有人知道正确答案。每个人都足够接近,但仍然是一种纯粹的哲学。 最近的,即最好的是: (s1 + s2) - (s1 * s2)。 这是一种非常好的方法,特别是对于MCU。

所以,算法是:

  1. 找出您想要输出声音的音量。 它可以是其中一个信号的平均值或最大值。
    factor = average(s1) 您假设两个信号都已正常,没有溢出32767.0
  2. 使用此因子标准化两个信号:
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  3. 将它们添加到一起并使用相同的因子对结果进行标准化
    output = ((s1+s2)/max(s1+s2))*factor
  4. 请注意,在步骤1之后,您实际上不需要返回到整数,您可以使用-1.0到1.0间隔的浮点数,并使用先前选择的功率因子将返回值应用于最后的整数。 我希望我现在没有弄错,因为我赶时间。

答案 6 :(得分:6)

你也可以给你自己一些头部空间的算法,如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级别,并使剪辑平滑到一个漂亮的曲线。产生谐波失真,听起来比削波更好,在某些情况下可能是理想的。

答案 7 :(得分:4)

如果您需要这样做,我建议您查看开源软件混音器实现,至少对于理论而言。

一些链接:

Audacity

GStreamer

实际上你应该使用图书馆。

答案 8 :(得分:4)

将样本转换为-1.0到+1.0之间的浮点值,然后:

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

答案 9 :(得分:3)

你把它们加在一起是对的。您可以随时扫描两个文件的总和以获得峰值点,如果它们达到某种阈值(或者它的平均值及其周围点达到阈值),则可以缩小整个文件的数量。

答案 10 :(得分:2)

我认为,只要这些流不相关,你就不应该担心太多,你应该能够通过削波来解决问题。如果您真的担心剪辑点处的失真,那么软限幅器可能会正常工作。

答案 11 :(得分:2)

  

将样本转换为-1.0到+1.0之间的浮点值,然后:

     

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

| s1 + s2 |时会引入严重失真接近1.0(至少当我在混合简单的正弦波时尝试它时)。 我在几个地方读到了这个建议,但我认为这是一种无用的方法。

当波浪'混合'时,身体会发生什么,他们的放大器会增加,就像这里提到的许多海报一样。

  • clip(也会扭曲结果)或
  • 将您的16位值汇总为32位数字,然后除以您的来源数量(这就是我所建议的,因为这是我知道避免扭曲的唯一方法)

答案 12 :(得分:1)

由于您的个人资料表明您在嵌入式系统中工作,我将假设浮点运算并不总是一个选项。

> So what's the correct method to add these sounds together in my software mixer?

正如您所猜测的那样,如果您不想丢失源上的音量,添加和剪辑是正确的方法。对于int16_t的样本,您需要将总和设为int32_t,然后限制并转换回int16_t

> Am I wrong and the correct method is to lower the volume of each by half?

是。音量减半在某种程度上是主观的,但你可以看到这里和那里的音量减半(响度)减少了约10 dB(将功率除以10,或将样本值除以3.16)。但你的意思是将样本值降低一半。这是一个6 dB的减少,明显减少,但不如音量减半(响度表there非常有用)。

通过这个6 dB的减少,你将避免所有削波。但是当你想要更多输入通道时会发生什么?对于四个通道,您需要将输入值除以4,即降低12 dB,从而减少每个通道响度的一半。

> Do I need to add a compressor/limiter or some other processing stage to 
get the volume and mixing effect I'm trying for?

您希望在输入信号上混合,而不是剪辑,而不是失去响度。这是不可能的,不是没有某种扭曲。

正如Mark Ransom所建议的那样,避免削波而不会损失每通道6 dB的解决方案是在“添加和削波”和“平均”之间的某个位置。

这是两个来源:添加,除以介于1和2之间(将范围从[-65536,65534]减小到更小),然后限制。

如果您经常使用此解决方案剪辑并且听起来太刺耳,那么您可能希望使用压缩器来软化限制膝盖。这有点复杂,因为您需要使分频因子取决于输入功率。首先单独尝试限制器,只有在对结果不满意时才考虑压缩器。

答案 13 :(得分:1)

我这样做了一次:我使用了浮点数(-1和1之间的样本),我初始化了一个值为1的“autoGain”变量。然后我将所有样本加在一起(也可能超过2)。然后我将输出信号乘以autoGain。如果乘法前信号之和的绝对值大于1,我将指定1 /此和值。这将有效地使自动增益小于1,假设为0.7,并且相当于一些操作员在看到整体声音变得太大时迅速调低主音量。然后我会在一段可调节的时间内添加到自动摄像机,直到它最终回到“1”(我们的操作员已从震动中恢复并且正在慢慢增加音量: - ))。

答案 14 :(得分:1)

// #include <algorithm>
// short ileft, nleft; ...
// short iright, nright; ...

// Mix
float hiL = ileft + nleft;
float hiR = iright + nright;

// Clipping
short left = std::max(-32768.0f, std::min(hiL, 32767.0f));
short right = std::max(-32768.0f, std::min(hiR, 32767.0f));

答案 15 :(得分:1)

我找到了一种新的方式来添加样本,它们永远不会超过给定的范围。基本思路是将-1到1之间的值转换为大约-Infinity到+ Infinity之间的范围,将所有内容添加到一起并反转初始转换。我想出了以下公式:

f(x)=-\frac{x}{|x|-1}

f'(x)=\frac{x}{|x|+1}

o=f'(\sum f(s))

我尝试了它并且确实有效,但是对于多个响亮的声音,所产生的音频听起来比仅仅将样本添加到一起并剪切每个太大的值更糟糕。我使用以下代码来测试它:

rak@zeta:/tmp/test$ ls
a  b  c
rak@zeta:/tmp/test$ echo 'use "/tmp/mwe.sml";' | sml
Standard ML of New Jersey v110.79 [built: Tue Aug  8 16:57:33 2017]
- [opening /tmp/mwe.sml]
[autoloading]
[library $SMLNJ-BASIS/basis.cm is stable]
[library $SMLNJ-BASIS/(basis.cm):basis-common.cm is stable]
[autoloading done]
a
b
c
val execpOutput = fn
  : string * string list -> string list * ?.POSIX_Process.exit_status option
val lsls = ["a\n","b\n","c\n"] : string list
val it = () : unit
-

答案 16 :(得分:1)

我做了以下事情:

MAX_VAL = Full 8 or 16 or whatever value
dst_val = your base audio sample
src_val = sample to add to base

Res = (((MAX_VAL - dst_val) * src_val) / MAX_VAL) + dst_val

将src的左侧净空乘以MAX_VAL归一化目标值并将其添加。它永远不会剪辑,永远不会那么响亮,声音绝对自然。

示例:

250.5882 = (((255 - 180) * 240) / 255) + 180

这听起来不错:)

答案 17 :(得分:0)

这个问题很旧,但这是IMO的有效方法。

  1. 转换两个样本的电量。
  2. 同时添加两个样本。
  3. 将其标准化。例如最大值不会超出您的限制。
  4. 转换幅度。

您可以一起进行前2个步骤,但是在第3步和第4步的第二遍中将需要最大值和最小值进行归一化。

我希望它能对某人有所帮助。

答案 18 :(得分:0)

感谢大家分享您的想法,最近我还做了一些与混音相关的工作。我也在这个问题上做过实验,可能对你有帮助:)。

请注意,我使用的是8Khz采样率&amp; ios RemoteIO AudioUnit中的16位采样(SInt16)声音。

在我的实验中,我发现的最佳结果与所有这些答案不同,但基本相同(如Roddy所示)

&#34; 您应该将它们一起添加,但将结果剪切到允许的范围以防止上溢/下溢&#34;。

但是添加没有溢出/下溢的最佳方法应该是什么?

关键想法 ::你有两个声波说A&amp; B,合成波C将是两个波A的superposition。 B.在有限位范围内的样本可能导致其溢出。所以现在我们可以在上行和下行计算最大限制交叉。在叠加波形的下方的最小限制交叉。现在我们将最大上限限制交叉减去叠加波形的上部,并将最小下限限制交叉添加到叠加波形的下部。 VOILA ......你已经完成了。

<强>步骤:

  1. 首先遍历数据循环一次,以获得上限交叉的最大值下限交叉的最小值
  2. 对音频数据进行另一次遍历,从正音频数据部分中减去最大值,并将最小值添加到音频数据的负数部分。
  3. 以下代码将显示实现。

    static unsigned long upSideDownValue = 0;
    static unsigned long downSideUpValue = 0;
    #define SINT16_MIN -32768
    #define SINT16_MAX 32767
    SInt16* mixTwoVoice (SInt16* RecordedVoiceData, SInt16* RealTimeData, SInt16 *OutputData, unsigned int dataLength){
    
    unsigned long tempDownUpSideValue = 0;
    unsigned long tempUpSideDownValue = 0;
    //calibrate maker loop
    for(unsigned int i=0;i<dataLength ; i++)
    {
        SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];
    
        if(SINT16_MIN < summedValue && summedValue < SINT16_MAX)
        {
            //the value is within range -- good boy
        }
        else
        {
           //nasty calibration needed
            unsigned long tempCalibrateValue;
            tempCalibrateValue = ABS(summedValue) - SINT16_MIN; // here an optimization comes ;)
    
            if(summedValue < 0)
            {
                //check the downside -- to calibrate
                if(tempDownUpSideValue < tempCalibrateValue)
                    tempDownUpSideValue = tempCalibrateValue;
            }
            else
            {
                //check the upside ---- to calibrate
                if(tempUpSideDownValue < tempCalibrateValue)
                    tempUpSideDownValue = tempCalibrateValue;
            }
        }
    }
    
    //here we need some function which will gradually set the value
    downSideUpValue = tempUpSideDownValue;
    upSideDownValue = tempUpSideDownValue;
    
    //real mixer loop
    for(unsigned int i=0;i<dataLength;i++)
    {
        SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];
    
        if(summedValue < 0)
        {
            OutputData[i] = summedValue + downSideUpValue;
        }
        else if(summedValue > 0)
        {
            OutputData[i] = summedValue - upSideDownValue;
        }
        else
        {
            OutputData[i] = summedValue;
        }
    }
    
    return OutputData;
    }
    

    它适用于我,我后来意图逐渐改变 upSideDownValue &amp;的价值 downSideUpValue 以获得更平滑的输出。

答案 19 :(得分:-1)

我想说只是将它们加在一起。如果你的16位PCM空间溢出,那么你正在使用的声音已经非常响亮,你应该将它们衰减。如果这会导致它们自身太软,请寻找另一种增加整体音量输出的方法,例如操作系统设置或转动扬声器上的旋钮。