Skip to content

混音算法探讨

一种简单的混音算法

实话实说,这个混音算法是我从网上找到的,不过效果还是挺不错的,公式就是 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音频采样数据,同样有不错的效果。

常用混音算法汇总

一、常见三种混音算法

  1. 直接相加法
    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];
    
        }
    
    }
    
    直接相加法混音是一种简单而常见的混音算法,其优缺点如下:

优点:

简单易懂:直接相加法混音算法非常简单,容易理解和实现,无需复杂的计算。

低计算开销:由于不涉及复杂的信号处理算法,直接相加法混音的计算开销相对较低,适用于实时处理等需要快速响应的应用场景。

缺点:

音量叠加问题:直接相加法混音仅是将多个音频信号简单地相加,可能导致音量过高,产生音频削波或失真的问题。在混音过程中可能需要对音频信号进行归一化处理,以避免超过合理范围的音量。

信号干扰:如果混音的音频信号存在频率冲突或相位差异,直接相加法混音可能会导致信号间的干扰或相互抵消。这可能会产生不良的音频效果或部分信号的消失。

  1. 简单平均法:
    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;
    
        }
    
    }
    
    其优缺点如下:

优点

简单易实现,计算速度快,能够自己按照比例混音

缺点:

无法对音频信号进行动态调整,可能导致混音后的声音过于平淡。

  1. 自适应权重法
    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);
    }
}