Подтвердить что ты не робот

Алгоритм для микширования звука

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

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

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

-Adam

4b9b3361

Ответ 1

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

В случае обрезания вы будете вводить искажение в звук, но это неизбежно. Вы можете использовать свой код отсечения для "обнаружения" этого условия и сообщить об этом пользователю/оператору (эквивалент красного "клипа" на микшере...)

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

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

Ответ 2

Существует статья о смешении здесь. Мне было бы интересно узнать, что другие думают об этом.

Ответ 3

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

Ответ "ticked": добавьте вместе и клип правильно, но нет, если вы хотите избежать обрезки.

Ответ на ссылку начинается с работоспособного алгоритма voodoo для двух положительных сигналов в [0,1], но затем применяется некоторая очень ошибочная алгебра, чтобы получить полностью неправильный алгоритм для подписанных значений и 8-битных значений. Алгоритм также не масштабируется до трех или более входов (произведение сигналов будет уменьшаться при увеличении суммы).

So - преобразовать входные сигналы в float, масштабировать их до [0,1] (например, подписанное 16-битное значение станет   
float v = ( s + 32767.0 ) / 65536.0 (close enough...)) и затем суммируйте их.

Чтобы масштабировать входные сигналы, вы, вероятно, должны выполнять некоторую фактическую работу, а не умножать или вычитать значение вуду. Я бы предложил сохранить средний объем, а затем, если он начнет дрейфовать высоко (выше 0,25) или ниже (ниже 0,01), начните применять значение масштабирования в зависимости от объема. Это, по сути, становится автоматической реализацией уровня и масштабируется с любым количеством входов. Лучше всего, в большинстве случаев это не будет бесполезно с вашим сигналом.

Ответ 4

Большинство приложений микширования звука будут смешивать их с номерами с плавающей запятой (32 бит достаточно хорош для микширования небольшого количества потоков). Переведите 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)

Ответ 5

"Тихо пополам" не совсем корректно. Из-за логарифмического ответа уха, разделение выборок пополам сделает его более тихим на 6 дБ - безусловно, заметным, но не катастрофическим.

Возможно, вы захотите пойти на компромисс, умножив его на 0,75. Это сделает его на 3 дБ более тихим, но уменьшит вероятность переполнения и уменьшит искажения, когда это произойдет.

Ответ 6

Я не могу поверить, что никто не знает правильного ответа. Все достаточно близки, но все же, чистая философия. Ближайший, то есть лучший, был: (s1 + s2) - (s1 * s2). Это отличный подход, особенно для MCU.

Итак, алгоритм идет:

  • Определите громкость, в которой вы хотите получить выходной звук. Это может быть средний или максимум одного из сигналов.
    factor = average(s1) Вы считаете, что оба сигнала уже в порядке, не переполняет 32767.0
  • Нормализовать оба сигнала с этим коэффициентом:
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  • Добавьте их вместе и нормализовать результат с тем же коэффициентом
    output = ((s1+s2)/max(s1+s2))*factor

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

Ответ 7

Вы также можете купить себе запас высоты с помощью алгоритма, такого как y = 1.1x - 0.2x ^ 3 для кривой, и с крышкой сверху и снизу. Я использовал это в Hexaphone, когда игрок воспроизводит несколько заметок вместе (до 6).

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 и сгладить клип до хорошей кривой. Производит гармонические искажения, которые звучат лучше, чем отсечение, и могут быть желательными в некоторых случаях.

Ответ 8

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

Некоторые ссылки:

Audacity

GStreamer

На самом деле, вероятно, вы должны использовать библиотеку.

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

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

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

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

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

Ответ 13

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

> So what 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] до чего-то меньшего), а затем ограничение.

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

Ответ 14

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

Ответ 15

// #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));

Ответ 16

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

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

И это звучит хорошо:)

Ответ 17

Я нашел новый способ добавления образцов таким образом, чтобы они никогда не превышали заданный диапазон. Основная идея состоит в том, чтобы преобразовать значения в диапазоне от -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;
}

Ответ 18

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

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

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

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

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

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

Шаги:

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

следующий код покажет реализацию.

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, чтобы получить более плавный результат.

Ответ 19

Этот вопрос старый, но вот действительный метод ИМО.

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

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

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

Ответ 20

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