混音算法探讨
一种简单的混音算法
实话实说,这个混音算法是我从网上找到的,不过效果还是挺不错的,公式就是
C = A + B - (A * B >> 0x10)
A和B就是两路不同的音频数据,C就是混音后的音频数据,当然,处理后,还需要对C进行防止数据溢出的处理,否则,可能会有爆音。
如果是16bit音频数据,就是:
if (C > 32767) C = 32767;else if (C < -32768) C = -32768;
如果是float音频数据,就是:
if (C > 1) C = 1;else if (C < -1) C = -1;
这个算法针对的是16bit的音频采样数据,我实验的结果是:对float音频采样数据,同样有不错的效果。
常用混音算法汇总
一、常见三种混音算法
- 直接相加法 直接相加法混音是一种简单而常见的混音算法,其优缺点如下:
优点:
简单易懂:直接相加法混音算法非常简单,容易理解和实现,无需复杂的计算。
低计算开销:由于不涉及复杂的信号处理算法,直接相加法混音的计算开销相对较低,适用于实时处理等需要快速响应的应用场景。
缺点:
音量叠加问题:直接相加法混音仅是将多个音频信号简单地相加,可能导致音量过高,产生音频削波或失真的问题。在混音过程中可能需要对音频信号进行归一化处理,以避免超过合理范围的音量。
信号干扰:如果混音的音频信号存在频率冲突或相位差异,直接相加法混音可能会导致信号间的干扰或相互抵消。这可能会产生不良的音频效果或部分信号的消失。
- 简单平均法: 其优缺点如下:
优点:
简单易实现,计算速度快,能够自己按照比例混音
缺点:
无法对音频信号进行动态调整,可能导致混音后的声音过于平淡。
- 自适应权重法
自适应权重法相对于上面两种更为复杂。其优缺点如下:
void adaptiveWeightingMixing(float *input1, float *input2, float *output, int length) { float factor = 1;//衰减因子 初始值为1 float MAX = 3.4028235E38; float MIN = -3.4028235E38; float mixVal; for (int i = 0; i < length; i++) { mixVal = (input1[i]+input2[i])*factor; if (mixVal>MAX) { factor = MAX/mixVal; mixVal = MAX; } if (mixVal<MIN){ factor = MIN/mixVal; mixVal = MIN; } if (factor < 1) { //SETPSIZE为f的变化步长,通常的取值为(1-f)/VALUE,此处取SETPSIZE 为 32 VALUE值可以取 8, 16, 32,64,128. factor += (1 - factor) / 32; } output[i] = mixVal; } }
优点:
动态调整权重:
能通过衰减因子factor动态调整音频信号的权重,以避免混音溢出边界(超出最大或最小值)。这样可以有效避免音频削波或失真的问题。
提供动态范围控制:通过根据混音结果调整衰减因子f的大小,自适应权重法可以对混音信号的动态范围进行控制,使其适应不同音频输入的音量差异。
缺点(相对而言):
算法复杂度较高:相较于简单相加法,自适应权重法需要进行较多的条件判断和计算,因此算法复杂度较高,可能对系统性能产生一定影响。
可能引入音频伪影:在自适应权重法中,权重的动态调整过程可能引入一定的音频伪影。这是由于频域的快速变化导致的,可能会对音频质量产生一定的影响。
二、代码实现案例
代码获取,公主号(gh_bffeac6359e7)回复:c语言实现混音算法代码
通过tinywav第三方库实现wav的读写,读取两个wav文件并选择混音模式,混音后写入新的wav文件,以下是代码一部分。
#include <math.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "../inc/tinywav.h"
#define _USE_MATH_DEFINES
#define BLOCK_SIZE 240
#define sampleRate 48000
enum mix_style_
{
Additive_Mixing = 1,
Average_Mixing = 2,
Adaptive_Weighting_Mixing = 3
};
void additiveMixing(const float* input1, const float* input2, float* output, int length)
{
for (int i = 0; i < length; i++)
{
output[i] = input1[i] + input2[i];
}
}
void averageMixing(float *input1, float *input2, float *output, int length)
{
float gain1 = 1;
float gain2 = 0.5;
for (int i = 0; i < length; i++)
{
output[i] = (input1[i] * gain1 + input2[i] * gain2) / 2.0f;
}
}
void adaptiveWeightingMixing(float *input1, float *input2, float *output, int length)
{
float factor = 1;//衰减因子 初始值为1
float MAX = 3.4028235E38;
float MIN = -3.4028235E38;
float mixVal;
for (int i = 0; i < length; i++)
{
mixVal = (input1[i]+input2[i])*factor;
if (mixVal>MAX)
{
factor = MAX/mixVal;
mixVal = MAX;
}
if (mixVal<MIN){
factor = MIN/mixVal;
mixVal = MIN;
}
if (factor < 1)
{
//SETPSIZE为f的变化步长,通常的取值为(1-f)/VALUE,此处取SETPSIZE 为 32 VALUE值可以取 8, 16, 32,64,128.
factor += (1 - factor) / 32;
}
output[i] = mixVal;
}
}
int main(int argc, char *argv[])
{
char* outputPath = "./wav_file/output.wav";
if (argc < 4)
{
printf("open failed,please input such as: ./mix test1.wav test2.wav 1~3 \n");
printf("1.procress with Additive Mixing\n");
printf("2.procress with Average Mixing\n");
printf("3.procress with Adaptive Weighting Mixing\n");
return -1;
}
TinyWav twReaderWav1,twReaderWav2;
tinywav_open_read(&twReaderWav1, argv[1], TW_INLINE);
tinywav_open_read(&twReaderWav2, argv[2], TW_INLINE);
int16_t wav1_channels = twReaderWav1.numChannels;
int32_t wav1_samplerate = twReaderWav1.h.SampleRate;
TinyWavSampleFormat wav1_sampFmt = twReaderWav1.sampFmt;
TinyWavChannelFormat wav1_chanfmt = twReaderWav1.chanFmt;
int16_t wav2_channels = twReaderWav2.numChannels;
int32_t wav2_samplerate = twReaderWav2.h.SampleRate;
TinyWavSampleFormat wav2_sampFmt = twReaderWav2.sampFmt;
TinyWavChannelFormat wav2_chanfmt = twReaderWav2.chanFmt;
if(wav1_channels != 1 || wav1_samplerate!=48000 || wav2_channels != 1 || wav2_samplerate!=48000)
{
printf("wav channel must be 1 channel and samplerate 48000\n");
return -1;
}
TinyWav twWriter;
tinywav_open_write(&twWriter, 1, 48000, TW_INT16, TW_INLINE, outputPath);
int wav1_totalNumSamples = twReaderWav1.numFramesInHeader;
int wav1_samplesProcessed = 0;
int wav2__totalNumSamples = twReaderWav2.numFramesInHeader;
int wav2_samplesProcessed = 0;
int wav1_block_count = 0;
int wav2_block_count = 0;
float wav1_data[BLOCK_SIZE] = {0};
float wav2_data[BLOCK_SIZE] = {0};
float out_data [BLOCK_SIZE] = {0};
int mix_style = atoi(argv[3]);
while (wav1_samplesProcessed < wav1_totalNumSamples)
{
int wav1_samplesRead = tinywav_read_f(&twReaderWav1, wav1_data, BLOCK_SIZE);
wav1_samplesProcessed += wav1_samplesRead;
if(wav2_samplesProcessed < wav2__totalNumSamples)
{
int wav2_samplesRead = tinywav_read_f(&twReaderWav2, wav2_data, BLOCK_SIZE);
wav2_samplesProcessed += wav2_samplesRead;
switch (mix_style)
{
case Additive_Mixing:
additiveMixing(wav1_data,wav2_data,out_data,BLOCK_SIZE);
break;
case Average_Mixing:
averageMixing(wav1_data,wav2_data,out_data,BLOCK_SIZE);
break;
case Adaptive_Weighting_Mixing:
adaptiveWeightingMixing(wav1_data,wav2_data,out_data,BLOCK_SIZE);
break;
default:
break;
}
int samplesWritten = tinywav_write_f(&twWriter, out_data, BLOCK_SIZE);
}
}
tinywav_close_read(&twReaderWav1);
tinywav_close_read(&twReaderWav2);
tinywav_close_write(&twWriter);
return 0;
}
两种算法的32bit移植
16bit与32bit的算法是一致的,只不过获取音频的数据方法有点区别
/************************************************
For n-bit sampling audio signal,If both A and B
are negative Y = A * B - (A * B / (-(pow(2, n-1) -1)))
else Y = A * B - (A * B / (POW(2,n-1)))
************************************************/
float Mix_001(const float *a, const float *b)
{
float c;
if(*a < 0 && *b < 0)
c = *a + *b - ((*a) * (*b) / -(pow(2,32-1) -1));
else
c = *a + *b - ((*a) * (*b) / (pow(2,32-1)));
c = c > 1 ? 1 : c;
c = c < -1 ? -1 : c;
return c;
}
/****************************
*线性叠加后求平均值
*优点:不会产生溢出,噪音较小
*缺点:衰减过大,影响通话质量
****************************/
float Mix_002(const float *a, const float *b)
{
return (*a + *b) / 2;
}
int main(int argc, char **argv)
{
/*仅支持音频数据为FLTP类型,即一个采样点占4个字节(32bits)*/
if(argc < 4)
{
fprintf(stderr,"Support fltp format,Use it like this:\n");
fprintf(stderr," %s in_1.pcm in_2.pcm out.pcm\n",argv[0]);
return 0;
}
FILE *in_0 = fopen(argv[1],"rb");
FILE *in_1 = fopen(argv[2],"rb");
FILE *out = fopen(argv[3],"wb");
char buf_0[4];
char buf_1[4];
while(1)
{
//读取一个声道的一个采样点
int ret_0 = fread(buf_0,4,1,in_0);
int ret_1 = fread(buf_1,4,1,in_1);
printf("ret_0=%d,ret_1=%d\n",ret_0,ret_1);
if(ret_0 == 0 || ret_1 == 0)
break;
//转换成float类型
float *a = (float*)buf_0;
float *b = (float*)buf_1;
float c = Mix_001(a,b);
//float c = Mix_002(a,b);
fwrite(&c,4,1,out);
}
}