Алгоритм микширования звука - PullRequest
56 голосов
/ 18 декабря 2008

У меня есть два необработанных звуковых потока, которые мне нужно добавить вместе. Для целей этого вопроса мы можем предположить, что они имеют одинаковый битрейт и битовую глубину (скажем, 16-битная выборка, частота выборки 44,1 кГц).

Очевидно, что если я просто сложу их вместе, я переполню и опустошу свое 16-битное пространство. Если я сложу их вместе и разделю на два, то громкость каждого из них уменьшится вдвое, что неправильно с точки зрения звука - если два человека говорят в комнате, их голоса не становятся тише наполовину, и микрофон может их выбрать оба без удара ограничителя.

  • Итак, как правильно добавить эти звуки в мой программный микшер?
  • Я ошибаюсь, и правильный метод - уменьшить громкость каждого из них вдвое?
  • Нужно ли мне добавлять компрессор / лимитер или какой-либо другой этап обработки, чтобы получить объем и эффект микширования, который я пробую?

-Adam

Ответы [ 20 ]

2 голосов
/ 27 июня 2017

преобразовать выборки в значения с плавающей запятой в диапазоне от -1,0 до +1,0, затем:

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

Внесет сильное искажение, когда | s1 + s2 | подход 1.0 (по крайней мере, когда я попробовал это, смешивая простые синусоиды). Я прочитал эту рекомендацию в нескольких местах, но, по моему скромному мнению, это бесполезный подход.

Что происходит физически, когда волны «смешиваются», так это то, что их амплитуды добавляются, как это уже предлагалось на многих постерах. Либо

  • клип (также искажает результат) или
  • суммируйте ваши 16-битные значения в 32-битное число, а затем разделите на количество ваших источников (это я бы посоветовал, так как это единственный известный мне способ избежать искажений)
2 голосов
/ 18 декабря 2008

Я думаю, что до тех пор, пока потоки не коррелированы, вам не о чем беспокоиться, вы сможете обойтись с отсечением. Если вы действительно обеспокоены искажением в точках клипа, мягкий ограничитель, вероятно, будет работать нормально.

1 голос
/ 13 сентября 2017

Я нашел новый способ добавления семплов таким образом, чтобы они никогда не превышали заданный диапазон. Основная идея состоит в том, чтобы преобразовать значения в диапазоне от -1 до 1 в диапазон от приблизительно -Infinity до + Infinity, сложить все вместе и отменить начальное преобразование. Я придумал для этого следующие формулы:

f(x)=-\frac{x}{|x|-1}

f'(x)=\frac{x}{|x|+1}

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

Я опробовал его, и он работает, но для нескольких громких звуков результирующий звук звучит хуже, чем просто сложение сэмплов и отсечение каждого значения, которое слишком велико. Я использовал следующий код, чтобы проверить это:

#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;
}
1 голос
/ 18 июля 2017

Я сделал следующее:

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 голос
/ 20 августа 2013
// #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));
1 голос
/ 10 марта 2013

Я сделал это один раз: я использовал float (сэмплы между -1 и 1), и я инициализировал переменную "autoGain" со значением 1. Затем я бы добавил все сэмплы вместе (также может быть больше, чем 2). Тогда я бы умножил исходящий сигнал на autoGain. Если бы абсолютное значение суммы сигналов до умножения было бы больше 1, я бы присвоил 1 / это значение суммы. Это фактически сделает автоматическое усиление меньше 1, скажем, 0,7, и будет эквивалентно тому, что какой-то оператор быстро отключит основную громкость, как только увидит, что общий звук становится слишком громким. Затем я в течение регулируемого периода времени добавляю к автогеингу, пока он, наконец, не вернется к «1» (наш оператор оправился от шока и медленно увеличивает громкость: -)).

1 голос
/ 22 октября 2012

Поскольку в вашем профиле указано, что вы работаете во встроенных системах, я предполагаю, что операции с плавающей запятой не всегда возможны.

> 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 дБ (деление мощности на 10 или выборочных значений на 3,16). Но вы, очевидно, имеете в виду понизить выборочные значения вдвое. Это уменьшение на 6 дБ, заметное уменьшение, но не настолько, как уменьшение вдвое громкости (таблица громкости там очень полезна).

С этим уменьшением на 6 дБ вы избежите всякого ограничения. Но что происходит, когда вам нужно больше входных каналов? Для четырех каналов вам нужно будет разделить входные значения на 4, то есть уменьшить на 12 дБ, что приведет к уменьшению половины громкости для каждого канала.

> Do I need to add a compressor/limiter or some other processing stage to 
get the volume and mixing effect I'm trying for?

Вы хотите микшировать, а не обрезать и не терять громкость на входных сигналах. Это невозможно, не без каких-либо искажений.

Как предложил Марк Рэнсом, решение, позволяющее избежать ограничения, не теряя при этом 6 дБ на канал, заключается в том, чтобы попасть где-то между «добавлением и ограничением» и «усреднением».

Это для двух источников: сложение, деление где-то между 1 и 2 (уменьшите диапазон от [-65536, 65534] до чего-то меньшего), затем ограничение.

Если вы часто обрезаете это решение, и оно звучит слишком резко, то вам может понадобиться смягчить ограничивающее колено компрессором. Это немного сложнее, так как необходимо сделать коэффициент деления зависимым от входной мощности. Сначала попробуйте ограничитель в одиночку, и рассматривайте компрессор, только если вы недовольны результатом.

0 голосов
/ 06 декабря 2018

Этот вопрос старый, но здесь действительный метод IMO.

  1. Конвертируйте оба сэмпла в мощность.
  2. Добавьте оба сэмпла.
  3. Нормализуй это. Например, максимальное значение не превышает вашего лимита.
  4. Преобразовать обратно в амплитуду.

Вы можете сделать первые 2 шага вместе, но для нормализации во втором проходе для шагов 3 и 4 потребуется максимум и минимум

Надеюсь, это кому-нибудь поможет.

0 голосов
/ 15 октября 2014

Спасибо всем за то, что поделились своими идеями, в последнее время я также занимаюсь работой, связанной с микшированием звука. Я также экспериментировал с этим вопросом, может, он поможет вам, ребята:).

Обратите внимание, что я использую частоту дискретизации 8 кГц и звук 16 бит (SInt16) в ios RemoteIO AudioUnit.

В ходе моих экспериментов лучший результат, который я нашел, был чем-то отличным от всего этого ответа, но основной тот же (как Родди предлагает)

" Вы должны сложить их вместе, но закрепить результат в допустимом диапазоне, чтобы предотвратить переполнение / недополнение ".

Но каким должен быть лучший способ добавления без переполнения / недостаточного заполнения?

Ключевая идея :: У вас есть две звуковые волны, скажем, A & B, и результирующая волна C будет иметь суперпозицию двух волн A и B. Сэмпл в ограниченном битовом диапазоне может вызвать его переполнение. Итак, теперь мы можем вычислить максимальный кросс-предел в верхней части и минимальный кросс-предел в нижней части формы волны суперпозиции. Теперь мы вычтем максимальное пересечение верхнего предела в верхнюю часть формы волны суперпозиции и добавим минимальное пересечение нижнего предела в нижнюю часть формы волны суперпозиции. ВУЙЛА ... вы сделали.

Шаги:

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

у меня это нормально работает, позже я собираюсь постепенно изменить значение upSideDownValue & downSideUpValue , чтобы получить более плавный вывод.

0 голосов
/ 18 декабря 2008

Я бы сказал, просто сложите их вместе. Если вы переполняете свое 16-битное пространство PCM, то используемые вами звуки уже невероятно громкие, и вы должны их ослабить. Если это приведет к тому, что они станут слишком мягкими, поищите другой способ увеличения общей громкости, например, настройку ОС или поворот регулятора на ваших динамиках.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...