【问题标题】:Algorithm to mix sound混合声音的算法
【发布时间】:2010-09-27 10:09:51
【问题描述】:

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

显然,如果我只是将它们加在一起,我的 16 位空间就会溢出和下溢。如果我将它们加在一起并除以 2,那么每个音量都会减半,这在声音上是不正确的——如果两个人在一个房间里说话,他们的声音不会变小一半,麦克风可以拾取他们两者都没有达到限制器。

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

-亚当

【问题讨论】:

  • 同样的问题,但更好的答案:dsp.stackexchange.com/questions/3581/…
  • 我对此感到非常失望。在现实生活中,我总是听到这两种信号无论它们处于哪个阶段。但简单地添加两个反相波的样本将导致完全静音。没提过……
  • @jmendeth 阶段取消是真实的。将两个扬声器并排放置,并从一个中反转相位(交换电线)。你的贝司被破坏了。您没有完全取消的原因是您的扬声器不是点源并且您有两只耳朵。
  • 我知道,我知道...不过,当人们听到“混音”时,他们并不认为两种声音会根据相位相互抵消,从而导致静音。
  • 而且我不希望两台仪器根据“运气”取消频率,以使它们相位反转。

标签: algorithm audio


【解决方案1】:

如果您需要正确执行此操作,我建议您查看开源软件混音器实现,至少在理论上是这样。

一些链接:

Audacity

GStreamer

实际上,您可能应该使用库。

【讨论】:

  • Audacity 只会添加样本,生成剪辑(如果样本很高)。您必须手动调整每个轨道的增益以防止削波。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      您应该将它们加在一起,但将结果限制在允许的范围内以防止上溢/下溢。

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

      您可以实现一个更“合适”的压缩器/限制器,但在不了解您的确切应用的情况下,很难说它是否值得。

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

      【讨论】:

      • 这个答案是正确的,但是我用一些关于如何在下面实现自动级别控制的注释来修饰它(在我拥有评论权限之前写)。
      • @Kyberias 这没有意义;第一句话从字面上解释了要做什么。
      • OP 已经说明了这个答案的建议以及这样做的缺点是什么,从问题“显然,如果我只是将它们加在一起,我将溢出和下溢我的 16 位空间。” @user1881400
      【解决方案5】:

      将它们加在一起是对的。您始终可以扫描两个文件的总和以查找峰值点,如果它们达到某种阈值(或者如果它及其周围点的平均值达到阈值)则缩小整个文件

      【讨论】:

      • 我同意你的观点,但对于声音流来说并不实用,因为你无法窥视声音,也许窗口化的动态增益调整可以吗?
      【解决方案6】:

      “安静一半”并不完全正确。由于耳朵的对数响应,将样本分成两半会使其安静 6 分贝——当然很明显,但不是灾难性的。

      您可能希望通过乘以 0.75 来妥协。这将使它安静 3 分贝,但会减少溢出的机会,并在发生时减少失真。

      【讨论】:

      • 3 dB 更安静会使功率减半,因此将样本值除以 sqrt(2)。那是乘以 0.707 (1/sqrt(2)) 而不是 0.75。不过,我确实同意通过位移更容易实现乘以 0.75。
      • @Gauthier,我是近似的。
      • @JorisWeimar,他是绝对正确的,将功率减半需要除以 2 的平方根。尽管它在技术上是 -3.0103 db,但它的惯例称为 -3 db。再次,近似值。
      • 但是@JorisWeimar 它与 sqrt(2) 有一切!它是-3db 数字,它是 sqrt(2) 的近似值,而不是相反 - 我想我已经说清楚了。功率与电压的平方成正比,因此要将功率减半需要通过 sqrt(2) 将电压(信号)降低。这完全是巧合,大约为 -3 db,原因与 2^10 (1024) 非常接近 10^3 (1000) 的原因相同。
      • @JorisWeimar db 是 ratio 的测量值,对于 dbfs,它是 满量程幅度 与相关信号的比率。如果考虑到这一点,您的公式是完全正确的,比率是乘法因子。这就是我得到上面引用的数字的方式:20 * log(1/sqrt(2)) = -3.0103.
      【解决方案7】:

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

      【讨论】:

      • 当然可以,但是会增加削波的几率,所以请相应调整音量
      • 这是否给您@MarkHeath 引入了白噪声?
      • 通过将混合乘以 0.8... 你不只是让你的噪音水平接近“平均”吗?如果将混合的负值(例如 -0.5)乘以 0.8,它将更接近 0,换句话说,它将变得更高......所以您需要在相乘之前转换为 0+ 范围,或者 cmets “稍微降低音量”的说法并不准确。
      【解决方案8】:

      有一篇关于混音here的文章。我很想知道其他人对此有何看法。

      【讨论】:

      • 这很有趣。基本上它会进行加法运算,然后对信号进行非常简单的“压缩”以避免削波。问题是,即使不需要剪辑,这也会显着改变样本值。对于某些应用程序(可能是电话、游戏),这种方法可能会很好用。但是对于高端音频处理,它可以被认为是降低信号...
      • 这篇文章具有误导性(请参阅下面的回答)。如果你将示例值输入他的最终公式,你会得到糟糕的输出(他的代数很糟糕)。例如。静音输入为您提供 -1 输出。无论如何,它不会扩展到两个以上的输入,而且它是一种没有现实基础的巫毒算法。
      • 更改每个样本的体积是不明智的。并且该算法不正确,因为如果您有两个具有相同信号的通道,则这两个通道的混合应该与每个通道相同。但该算法给信号带来了损失。
      • 那篇文章显然是错误的,正如许多人所建议的那样。请停止投票,你只会误导人们。
      【解决方案9】:

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

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

      【讨论】:

      • 我想我得把那个弄明白了。似乎它可能是合适的,但如果输入为 1 和 -1,则结果为 1。不确定我是否要为此打破拉普拉斯,但如果您有任何关于为什么或如何工作的更多信息的参考,我希望能有一个良好的开端,
      • 另请注意,文章指出输入值介于 0 和 1 之间。
      【解决方案10】:

      您还可以使用 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 级,并将剪辑平滑成漂亮的曲线。产生谐波失真,这听起来比削波更好,在某些情况下可能是理想的。

      【讨论】:

      • 试过了,效果很好。处理剪辑的快速解决方案。
      • 另外,这个答案暗示你应该在混合之前转换为浮点数。
      • 这看起来很有趣。你从哪里得到这些魔法常数? (特别是 1.25 和 0.984375?)
      • 1.25 是我愿意接受的上限(125% 水平)。 0.984375 是我指定的公式中 x=1.25 的 y 值。
      • 记录一下:这是压缩(还有一点扩展)。
      【解决方案11】:

      我更愿意对两个排名靠前的回复中的一个发表评论,但由于我的名声微薄(我想),我不能。

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

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

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

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

      【讨论】:

      • 感谢您的留言!我认为这是值得回答的,但你现在有 50 个代表,所以你现在应该可以在网站上发表评论了。
      【解决方案12】:

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

      > 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】:

        我不敢相信没有人知道正确的答案。每个人都足够接近,但仍然是一种纯粹的哲学。 最接近的,即最好的是: (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

        请注意,在第 1 步之后,您实际上并不需要返回整数,您可以使用 -1.0 到 1.0 区间内的浮点数,并在最后使用先前选择的功率因数应用返回整数。 我希望我现在没有弄错,因为我很着急。

        【讨论】:

        • 这是错误的。例如。考虑 s1 和 s2 都是 0.5,s1+s2 => 1,max(s1, s2) 是 0.5,所以输出是 2。你已经过去了剪裁和天真的添加不会有。此外,0.25 和 0.25 产生相同的结果。
        【解决方案14】:

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

        【讨论】:

          【解决方案15】:
          // #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));
          

          【讨论】:

            【解决方案16】:

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

            请注意,我在 ios RemoteIO AudioUnit 中使用 8Khz 采样率和 16 位采样 (SInt16) 声音。

            在我的实验中,我发现的最佳结果与所有这些答案都不同,但基本是相同的(正如Roddy 建议的那样)

            "您应该将它们相加,但将结果剪辑到允许的范围内以防止上溢/下溢"。

            但是在没有上溢/下溢的情况下添加的最佳方法应该是什么?

            Key Idea:: 你有两个声波A & B,合成的波C 将是两个波A & B 的superposition。在有限位范围下的采样可能会导致它溢出。所以现在我们可以计算叠加波形上方的最大交叉点和下方的最小交叉点。现在我们将在叠加波形的上部减去最大上行限制交叉,并将最小下行限制交叉添加到叠加波形的下部。瞧……你完成了。

            步骤:

            1. 首先遍历您的数据循环一次以获得上限交叉的最大值和下限交叉的最小值
            2. 再次遍历音频数据,从音频数据的正数部分减去最大值,在音频数据的负数部分加上最小值

            以下代码将显示实现。

            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;
            }
            

            它对我来说很好用,我后来打算逐渐改变 upSideDownValuedownSideUpValue 的值以获得更平滑的输出。

            【讨论】:

            • 到目前为止,我尝试使用来自各个来源的 4 pcm 值,这对我来说没问题。没有尝试过更多。
            【解决方案17】:

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

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

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

            当波“混合”时,物理上发生的事情是它们的振幅相加,就像这里的许多海报已经建议的那样。 要么

            • 剪辑(也会扭曲结果)或
            • 将您的 16 位值汇总为 32 位数字,然后除以您的源数(我建议这样做,因为这是我知道的唯一避免失真的方法)

            【讨论】:

              【解决方案18】:

              我做了以下事情:

              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
              

              这听起来不错:)

              【讨论】:

              • 你能解释一下吗,大概用四个例子,其中 dst 和 src 都是高值和低值,所以很容易理解这个算法在做什么,为什么?
              【解决方案19】:

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

              我试过了,它确实有效,但是对于多个响亮的声音,生成的音频听起来比仅仅将样本加在一起并剪裁每个太大的值更糟糕。我使用以下代码对此进行了测试:

              #include <math.h>
              #include <stdio.h>
              #include <float.h>
              #include <stddef.h>
              #include <stdint.h>
              #include <string.h>
              #include <stdbool.h>
              #include <sndfile.h>
              
              // fabs wasn't accurate enough
              long double ldabs(long double x){
                return x < 0 ? -x : x;
              }
              
              // -Inf<input<+Inf, -1<=output<=+1
              long double infiniteToFinite( long double sample ){
                // if the input value was too big, we'll just map it to -1 or 1
                if( isinf(sample) )
                  return sample < 0 ? -1. : 1.;
                long double ret = sample / ( ldabs(sample) + 1 );
                // Just in case of calculation errors
                if( isnan(ret) )
                  ret = sample < 0 ? -1. : 1.;
                if( ret < -1. )
                  ret = -1.;
                if( ret > 1. )
                  ret = 1.;
                return ret;
              }
              
              // -1<=input<=+1, -Inf<output<+Inf
              long double finiteToInfinite( long double sample ){
                // if out of range, clamp to 1 or -1
                if( sample > 1. )
                  sample = 1.;
                if( sample < -1. )
                  sample = -1.;
                long double res = -( sample / ( ldabs(sample) - 1. ) );
                // sample was too close to 1 or -1, return largest long double
                if( isinf(res) )
                  return sample < 0 ? -LDBL_MAX : LDBL_MAX;
                return res;
              }
              
              // -1<input<1, -1<=output<=1 | Try to avoid input values too close to 1 or -1
              long double addSamples( size_t count, long double sample[] ){
                long double sum = 0;
                while( count-- ){
                  sum += finiteToInfinite( sample[count] );
                  if( isinf(sum) )
                    sum = sum < 0 ? -LDBL_MAX : LDBL_MAX;
                }
                return infiniteToFinite( sum );
              }
              
              #define BUFFER_LEN 256
              
              int main( int argc, char* argv[] ){
              
                if( argc < 3 ){
                  fprintf(stderr,"Usage: %s output.wav input1.wav [input2.wav...]\n",*argv);
                  return 1;
                }
              
                {
                  SNDFILE *outfile, *infiles[argc-2];
                  SF_INFO sfinfo;
                  SF_INFO sfinfo_tmp;
              
                  memset( &sfinfo, 0, sizeof(sfinfo) );
              
                  for( int i=0; i<argc-2; i++ ){
                    memset( &sfinfo_tmp, 0, sizeof(sfinfo_tmp) );
                    if(!( infiles[i] = sf_open( argv[i+2], SFM_READ, &sfinfo_tmp ) )){
                      fprintf(stderr,"Could not open file: %s\n",argv[i+2]);
                      puts(sf_strerror(0));
                      goto cleanup;
                    }
                    printf("Sample rate %d, channel count %d\n",sfinfo_tmp.samplerate,sfinfo_tmp.channels);
                    if( i ){
                      if( sfinfo_tmp.samplerate != sfinfo.samplerate
                       || sfinfo_tmp.channels != sfinfo.channels
                      ){
                        fprintf(stderr,"Mismatching sample rate or channel count\n");
                        goto cleanup;
                      }
                    }else{
                      sfinfo = sfinfo_tmp;
                    }
                    continue;
                    cleanup: {
                      while(i--)
                        sf_close(infiles[i]);
                      return 2;
                    }
                  }
              
                  if(!( outfile = sf_open(argv[1], SFM_WRITE, &sfinfo) )){
                    fprintf(stderr,"Could not open file: %s\n",argv[1]);
                    puts(sf_strerror(0));
                    for( int i=0; i<argc-2; i++ )
                      sf_close(infiles[i]);
                    return 3;
                  }
              
                  double inbuffer[argc-2][BUFFER_LEN];
                  double outbuffer[BUFFER_LEN];
              
                  size_t max_read;
                  do {
                    max_read = 0;
                    memset(outbuffer,0,BUFFER_LEN*sizeof(double));
                    for( int i=0; i<argc-2; i++ ){
                      memset( inbuffer[i], 0, BUFFER_LEN*sizeof(double) );
                      size_t read_count = sf_read_double( infiles[i], inbuffer[i], BUFFER_LEN );
                      if( read_count > max_read )
                        max_read = read_count;
                    }
                    long double insamples[argc-2];
                    for( size_t j=0; j<max_read; j++ ){
                      for( int i=0; i<argc-2; i++ )
                        insamples[i] = inbuffer[i][j];
                      outbuffer[j] = addSamples( argc-2, insamples );
                    }
                    sf_write_double( outfile, outbuffer, max_read );
                  } while( max_read );
              
                  sf_close(outfile);
                  for( int i=0; i<argc-2; i++ )
                    sf_close(infiles[i]);
                }
              
                return 0;
              }
              

              【讨论】:

              • 如果我在脑海中正确地看到了这一点,那么你在这里所做的就是在剪裁时降低精度,这可以解释为什么它听起来很糟糕。钳制在预期范围内正是裁剪的含义。
              【解决方案20】:

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

              1. 转换两个样本的功率。
              2. 添加两个样本。
              3. 对其进行标准化。比如最大值不超过你的限制。
              4. 在幅度上转换回来。

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

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多