混音算法整理

音频本质

  • 声音(sound)是由物体振动产生的声波。
  • 声音以波的形式振动(震动)传播。
  • 声音是声波通过任何介质传播形成的运动。

所以,声音本质上是一种波,波是指振动的传播。因此,声音的混合也就是波的叠加。

音频帧

音频帧的概念没有视频帧那么清晰。可以简单认为一个视频帧就是一个图片。但音频帧并不是一个声音,它可以认为是一段时间内若干个采样点组成的数据集合。音频帧跟编码格式相关,它是各个编码标准自己实现。
举例:

  • 一个AAC音频帧包含某段时间内1024个采样点相关数据。
  • 一个MP3音频帧为1152个字节的音频采样数据。
  • 一个PCM(未经编码的音频数据)来说,不需要音频帧的概念,根据采样率和采样精度就可以播放了。比如采样率为44.1kHZ,采样精度为16位的音频,你可以算出bitrate(比特率)是44100*16kbps,每秒的音频数据是固定的44100*16/8 字节。

混音注意事项

并非任何两路音频流都可以直接混合。
两路音视频流,必须符合以下条件才能混合:

1)格式相同,要解压成PCM格式;
2)采样率相同,要转换成相同的采样率。主流采样率包括:16k Hz、32k Hz、44.1k Hz和48k Hz;
3)帧长相同,帧长由编码格式决定,PCM没有帧长的概念,开发者自行决定帧长。为了和主流音频编码格式 的帧长保持一致,推荐采用20ms为帧长;
4)位深(Bit-Depth)或采样格式(Sample Format)相同,承载每个采样点数据的bit数目要相同;
5)声道数相同,必须同样是单声道或者双声道(立体声)。这样,把格式、采样率、帧长、位深和声道数对齐 了以后,两个音频流就可以混合了。

最简单的混音—波的直接叠加

前面提到一个音频帧是由多个音频采样数据组成。在直接音频叠加的情况下,对于m个音频流(音波)混音后的一个音频帧,其中的每个音频采样样本output[i],与每个音频流input[j][i]的关系如下:

for(j = 0; j < m; j++ )
	output[i] += input[j][i];

其中,m 为输入音频流的个数,input[j]为第j个音频流, 所以, output[i]为混音后的一帧中第i个样本, input[j][i]为第j个输入音频流当前帧的第i个样本(若经过编码则输入音频流应在混音前通过解码等还原成线性的PCM音频流)。

通常的语音数据为16 bit(或者更少,如8 bit),即可以用C语言中的short 类型表示,其取值范围是[−32768,32767],可以预想到多个音频流直接线性叠加以后就有可能溢出,所以此种方式最后的结果可能会有溢出,产生噪音。

均值混音

两个连续平滑的波形叠加, 其结果也应该是平滑的.所以产生噪音的地方就是由叠加溢出的地方引入的.我们需要采用滤波来处理这些溢出部分,改善由于溢出所造成的质量下降.。如果将m个音频流的振幅全部降低m倍,则叠加后的最大振幅不会超过单个音频流的最大振幅,因此不会产生溢出。
因此,我们引入如下操作:
前面提到一个音频帧是由多个音频采样数据组成。在直接音频叠加的情况下,对于m个音频流(音波)混音后的一个音频帧,其中的每个音频采样样本output[i],与每个音频流input[j][i]的关系如下:

for(j = 0; j < m; j++ )
	output[i] += ( input[j][i] / m );

其中,m 为输入音频流的个数,input[j]为第j个音频流, 所以, output[i]为混音后的一帧中第i个样本, input[j][i]为第j个输入音频流当前帧的第i个样本(若经过编码则输入音频流应在混音前通过解码等还原成线性的PCM音频流)。

均值混音的作为最简单有效的做法,解决了音频叠加后产生噪音的问题,但是混音以后的声音会总体衰减,即整体音量变小,特别是某一路音频流的能量与其他路音频流的能量反差很大的情况下,音量会变得非常小,效果非常不理想。

归一化混音

为了解决溢出的问题,一个常用的方法就是使用更多的位数来表示音频数据的一个样本, 在混音完毕以后,再使用一些算法来降低其振幅,使其分布在16 bit所能表示的范围之内, 这种方法叫做归一化(Normalize).

通常使用32 bit来表示线性叠加以后的数据, 也就是C语言中的int类型, 实现简单,运算也比较快,更能满足很多路音频同时进行混音的需要. 进行滤波处理的另外一种常用方法就是截断,当发生上溢时, 混音后的值截断为能表示的最大值, 当发生下溢时,混音后的值截断为能表示的最小值

for(j = 0; j < m; j++ )
	output[i] += input[j][i];
if ( output[i] >  MAX )	 output[i] = MAX;
if ( output[i] <  MIN )	 output[i] = MIN;

这种方式实现简单, 快速, 效率很高。但是可以看出,这种方法相当于在最大和最小的临界值处切强行切断波形,非常生硬,会造成较大的波形失真, 听觉上引起如嘈杂,出现突发刺耳的爆破音等. 同时, 随着参与混音的人数增加,出现溢出的频率也不断上升,因此爆破音出现的频率也会不断上升, 实验证明,采用这种直接叠加的方式进行混音,一般不能突破 4 路输入音频流的限制, 否则将无法分辨语音流的内容.

改进后的归一化混音

均值混音的思想是使用一个衰减因子, 对每一路的音频数据进行衰减, 如果将m个音频流的振幅全部降低m倍,则叠加后的最大振幅不会超过单个音频流的最大振幅,因此不会产生溢出。

这里的思想是类似的,只不过衰减因子会随着数据而变化. 当可能溢出时,衰减因子比较小,使溢出的音频数据衰减以后处于临界值以内, 当没有溢出时, 衰减因子会慢慢增加, 尽量保持数据的平滑变化,也会让每个音频流的声音逐渐增大.而不是对于整帧使用同一个衰减因子来进行,这是不同于上面方法的地方,既保证了整体的声强不至于衰减太快, 又保证了较小的失真度.

float f = 1.0;
void mixing()
{
	for(j = 0; j < m; j++ )
		output[i] += input[j][i];
	int tmp = output[i]*f;
	if(tmp > MAX) 
		f=MAX/(float)output[i];
	else if (tmp < MIN)
		f=MIN/(float)output[i]; 
	output[i] *= f;
	float STEPSIZE = (1-f)/16;
	if(f<1) f+=STEPSIZE;
}

这里的STEPSIZE取值较大时,运算复杂度低,但语言平滑度不够细腻,STEPSIZE取值较小时,运算复杂度高,但语言平滑度比较细腻。

采用衰减因子的方式进行调整以后,混入 4 路音频流从听觉上基本感觉不到背景噪音,混入 5路的情况下, 仍然能清晰辨别各路的语音内容,不出现爆破音;混入 6 路到 9路的情况下仍然能保证语音质量,不会发生突变的爆破音, 能够满足视频会议的要求.

从算法执行效率上看, 与常规的混音算法比较,其时间复杂度并没有增加,具有同等的时间复杂度,只是调和系数法在计算过程中叠加时需要进行一次额外的乘法运算,并且发生溢出的情况下需要重新计算新的调和系数(整数除法运算),最后在算法的第三步需要进行一次加法运算(浮点数加法).因为涉及的数值不会很大,同时音频流的数据量较之视频等要小得很多。

参考文献

https://www.doc88.com/p-70383188302.html

你可能感兴趣的:(音频,算法,ffmpeg)