algorithm来混音

我有两个原始声音stream,我需要加在一起。 为了这个问题的目的,我们可以假定它们是相同的比特率和比特深度(比如16比特采样,44.1khz采样率)。

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

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

-亚当

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

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

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

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

这里有一篇关于混合的文章。 我很想知道别人怎么看这个。

我宁愿评论两个排名很高的答复之一,但由于我的名誉(我认为),我不能。

“勾号”的答案:加在一起,剪辑是正确的,但不是如果你想避免裁减。

链接的答案从一个可行的voodooalgorithm开始[0,1]中的两个正信号,然后应用一些非常有缺陷的代数来为有符号值和8位值导出完全不正确的algorithm。 该algorithm也不会缩放到三个或更多个input(信号的乘积将下降而总和增加)。

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

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

大多数audio混合应用程序将与浮点数进行混合(32位足够好,可以混合less量stream)。 将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) 

“安静一半”是不正确的。 由于耳朵的对数响应,将样本分成两半会使它更安静6分贝 – 当然是显而易见的,但不是灾难性的。

你可能要妥协0.75乘以。 这将使它3分贝更安静,但会减less溢出的机会,并减less发生时的扭曲。

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

所以,algorithm是:

  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的间隔使用浮点数,并将结尾处的返回值与先前select的功率因数一起应用。 我希望我现在没有错,因为我很匆忙。

你也可以用y = 1.1x – 0.2x ^ 3这样的algorithm为自己买一些空间,并在顶部和底部加盖。 当玩家一起弹奏多个音符(最多6个)时,我在Hexaphone中使用了这个function。

 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级,并平滑剪辑一个不错的曲线。 产生谐波失真,这听起来比削波好,在某些情况下可能是可取的。

如果你需要这样做,我会build议看看开源软件混音器的实现,至less在理论上。

一些链接:

大胆

的GStreamer

其实你可能应该使用一个库。

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

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

你把他们加在一起是对的。 您可以总是扫描两个文件的总和以得到最高点,如果遇到某种阈值(或者其平均值及其周边点达到阈值),则将整个文件缩小,

我认为,只要溪stream不相关,你不应该有太多的担心,你应该能够通过削减。 如果你真的担心在剪辑点失真,一个软限制器可能会工作正常。

我这样做了一次:我使用浮动(-1和1之间的样本),我初始化一个值为1的“autoGain”variables。然后我将所有的样本加起来(也可以是2以上)。 然后我会乘以传出的信号与autoGain。 如果乘法之前的信号之和的绝对值大于1,我将分配1 /这个和值。 这样可以使自动加号小于1,比如说0.7,相当于某个操作员一看到整体声音变得太大,就快速调低主音量。 然后,我会在可调整的时间段内添加自动加减,直到它最终回到“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)); 

由于你的configuration文件说你在embedded式系统中工作,我会假设浮点运算并不总是一个选项。

 > 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? 

是。 音量减半是有些主观的,但是你可以在这里和那里看到的是音量减半(响度)减less了大约10dB(功率除以10,或者样本值除以3.16)。 但是你的意思显然是把样本值降低一半。 这是一个6分贝的减less,明显的减less,但不是减半音量(响度表非常有用)。

有了这个6 dB的减less,你将避免所有的裁剪。 但是当你想要更多的input通道时会发生什么? 对于四个通道,您需要将input值除以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? 

你想混音,而不是剪辑,而不是input信号的响度。 这是不可能的,不是没有一些扭曲。

正如Mark Ransom所build议的那样,避免削波而不损失每个通道6 dB的解决scheme是在“增加和削波”和“平均”之间find一个位置。

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

如果您经常使用此解决scheme进行裁剪,听起来太刺耳,那么您可能需要使用压缩机软化极限弯曲。 这有点复杂,因为您需要根据input功率来确定分频因子。 首先尝试限制器,只有在对结果不满意的情况下才考虑使用压缩器。

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

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

当| s1 + s2 |时会引入严重失真 方法1.0(至less当我尝试混合简单的正弦波时)。 我在几个地方看过这个build议,但是我认为这是一个无用的方法。

当波浪“混合”时,身体发生的事情就是他们的声音增加了,就像这里已经提到的很多海报一样。 或

  • 剪辑(扭曲的结果以及)或
  • 总结你的16位值为32位数字,然后除以你的来源数量(这是我所build议的,因为这是我知道的唯一方法避免扭曲)

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

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

沿着我的实验,我发现的最好的结果是不同于所有这个答案,但基本是相同的(正如Roddy所build议的)

你应该把它们加在一起,但是将结果限制在允许的范围内以防止超出/下溢 ”。

但是,什么应该是最好的方式来添加没有溢出/下溢?

主要思想 ::你有两个声波说A和B,合成波C将两个波A和B的叠加 。有限的位范围内的样品可能会导致它溢出。 所以现在我们可以计算出叠加波形下边界的上限下限交叉最大交叉点。 现在我们将叠加波形的上部减去最大上限交叉 ,并将叠加波形下部的最小下限交叉添加到叠加波形的下部。 VOILA …你完成了。

脚步:

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

下面的代码将显示实现。

 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的值,以获得更平滑的输出。

我做了以下事情:

 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 

这听起来不错:)

我发现了一种新的方式来添加样本,使其不能超过给定的范围。 基本的想法是将范围在-1到1之间的值转换为大约-Infinity到+ Infinity之间的范围,将所有的东西加在一起并且反转初始转换。 我想出了以下公式:

F(X)=  -  \压裂{X} {| X | -1}

F'(X)= \压裂{X} {| X | +1}

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

我试了一下,它确实有效,但对于多个响亮的声音,结果audio听起来比只是将样本添加到一起并剪切每个太大的值都更糟糕。 我用下面的代码来testing这个:

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

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