programing

사운드 믹싱 알고리즘

nasanasas 2020. 12. 27. 11:05
반응형

사운드 믹싱 알고리즘


함께 추가해야하는 두 개의 원시 사운드 스트림이 있습니다. 이 질문의 목적을 위해 동일한 비트 전송률과 비트 심도 (예 : 16 비트 샘플, 44.1khz 샘플 속도)라고 가정 할 수 있습니다.

분명히 그것들을 함께 추가하면 16 비트 공간이 오버플로되고 언더 플로됩니다. 두 사람을 더하고 2로 나누면 각 볼륨이 절반으로 줄어 듭니다. 이는 음파 적으로 정확하지 않습니다. 두 사람이 한 방에서 말하면 그들의 목소리가 절반으로 조용해지지 않고 마이크가이를 선택할 수 있습니다. 리미터를 치지 않고 둘 다 올립니다.

  • 그렇다면 내 소프트웨어 믹서에서 이러한 사운드를 함께 추가하는 올바른 방법은 무엇입니까?
  • 내가 틀렸고 올바른 방법은 각각의 볼륨을 절반으로 낮추는 것입니까?
  • 원하는 볼륨 및 믹싱 효과를 얻으려면 컴프레서 / 리미터 또는 다른 처리 단계를 추가해야합니까?

-아담


함께 추가해야하지만 오버플로 / 언더 플로를 방지하기 위해 허용 범위에 결과를 클리핑합니다.

클리핑이 발생 하는 경우 오디오에 왜곡 발생 하지만 이는 피할 수 없습니다. 클리핑 코드를 사용하여이 상태를 "감지"하고 사용자 / 운영자에게보고 할 수 있습니다 (믹서의 빨간색 '클립'표시등과 동일합니다 ...).

더 "적절한"컴프레서 / 리미터를 구현할 수 있지만 정확한 응용 프로그램을 알지 못하면 그만한 가치가 있는지 말하기가 어렵습니다.

많은 오디오 처리를 수행하는 경우 오디오 레벨을 부동 소수점 값으로 표시하고 프로세스가 끝날 때만 16 비트 공간으로 돌아갈 수 있습니다. 고급 디지털 오디오 시스템은 종종 이러한 방식으로 작동합니다.


나는 두 개의 높은 순위 응답 중 하나에 대해 언급하고 싶지만 내 평판이 부족하기 때문에 (나는 추측) 할 수 없습니다.

"틱"대답 : 함께 추가하고 클립이 정확하지만 클리핑을 피하려면 그렇지 않습니다.

링크에 대한 답은 [0,1]의 두 양의 신호에 대해 실행 가능한 부두 알고리즘으로 시작하지만 매우 잘못된 대수를 적용하여 부호있는 값과 8 비트 값에 대해 완전히 잘못된 알고리즘을 도출합니다. 알고리즘은 또한 세 개 이상의 입력으로 확장되지 않습니다 (합이 증가하는 동안 신호의 곱은 감소 함).

따라서 입력 신호를 플로트로 변환하고 [0,1]로 스케일링합니다 (예 : 부호있는 16 비트 값이되고
float v = ( s + 32767.0 ) / 65536.0 (close enough...))
합산됩니다.

입력 신호의 크기를 조정하려면 부두 값을 곱하거나 빼는 대신 실제 작업을 수행해야합니다. 실행 평균 볼륨을 유지 한 다음 높거나 (0.25 이상) 또는 낮게 (0.01 미만) 표류하기 시작하면 볼륨에 따라 스케일링 값을 적용하는 것이 좋습니다. 이것은 본질적으로 자동 레벨 구현이되며 입력 수에 관계없이 확장됩니다. 무엇보다도 대부분의 경우 신호를 전혀 방해하지 않습니다.


여기 에 믹싱에 대한 기사가 있습니다 . 다른 사람들이 이것에 대해 어떻게 생각하는지 알고 싶습니다.


대부분의 오디오 믹싱 응용 프로그램은 부동 소수점 숫자로 믹싱을 수행합니다 (32 비트는 적은 수의 스트림을 믹싱하기에 충분합니다). 16 비트 샘플을 16 비트 세계에서 전체 스케일을 나타내는 -1.0에서 1.0 범위의 부동 소수점 숫자로 변환합니다. 그런 다음 샘플을 합산하면 충분한 여유 공간이 생깁니다. 마지막으로, 값이 전체 스케일을 초과하는 샘플이있는 경우 전체 신호를 감쇠하거나 하드 제한 (값을 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)

"반으로 조용히"는 정확하지 않습니다. 귀의 대수 반응으로 인해 샘플을 반으로 나누면 6db가 더 조용해집니다. 확실히 눈에 띄지 만 비참하지는 않습니다.

0.75를 곱하여 타협 할 수 있습니다. 그러면 3dB 더 조용해 지지만 오버플로 가능성이 줄어들고 왜곡이 발생할 때도 줄어 듭니다.


아무도 정답을 모른다는 것을 믿을 수 없습니다. 모두가 충분히 가깝지만 여전히 순수한 철학입니다. 가장 가까운, 즉 최고는 : (s1 + s2)-(s1 * s2). 특히 MCU의 경우 탁월한 접근 방식입니다.

따라서 알고리즘은 다음과 같습니다.

  1. 출력 사운드를 원하는 볼륨을 찾으십시오. 신호 중 하나의 평균 또는 최대 값이 될 수 있습니다.
    factor = average(s1)두 신호 모두 이미 정상이며 32767.0을 넘치지 않는다고 가정합니다.
  2. 다음 요인으로 두 신호를 모두 정규화합니다.
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  3. 그들을 함께 더하고 동일한 요인으로 결과를 정규화하십시오.
    output = ((s1+s2)/max(s1+s2))*factor

1 단계 이후에는 실제로 정수로 되돌릴 필요가 없습니다. -1.0에서 1.0 간격으로 부동 소수점으로 작업하고 이전에 선택한 역률로 끝에서 정수에 반환 값을 적용 할 수 있습니다. 지금 실수하지 않았 으면 좋겠어요. 서둘러 요.


곡선에 대해 y = 1.1x-0.2x ^ 3과 같은 알고리즘을 사용하고 상단과 하단에 캡이있는 헤드 룸을 구매할 수도 있습니다. 플레이어가 여러 음을 함께 연주 할 때 (최대 6 개) Hexaphone 에서 이것을 사용했습니다 .

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 레벨까지 올라갈 수 있고 클립을 멋진 곡선으로 매끄럽게 만듭니다. 고조파 왜곡을 생성합니다. 이는 클리핑보다 사운드가 더 좋으며 상황에 따라 바람직 할 수 있습니다.


이 작업을 올바르게 수행해야한다면 적어도 이론적으로는 오픈 소스 소프트웨어 믹서 구현을 살펴 보는 것이 좋습니다.

일부 링크 :

대담

GStreamer

실제로 라이브러리를 사용해야합니다.


샘플을 -1.0에서 +1.0 사이의 부동 소수점 값으로 변환 한 다음 :

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

함께 추가하는 것이 맞습니다. 항상 두 파일의 합계에서 피크 지점을 검색하고, 어떤 종류의 임계 값에 도달하면 전체 파일을 축소 할 수 있습니다 (또는 파일의 평균과 주변 지점이 임계 값에 도달 한 경우).


스트림이 서로 관련이없는 한 걱정할 것이 너무 많으면 안되며 클리핑으로 처리 할 수 ​​있어야한다고 생각합니다. 클립 포인트의 왜곡이 정말로 걱정된다면 소프트 리미터가 정상적으로 작동 할 것입니다.


샘플을 -1.0에서 +1.0 사이의 부동 소수점 값으로 변환 한 다음 :

아웃 = (s1 + s2)-(s1 * s2);

| s1 + s2 | 일 때 심한 왜곡이 발생합니다. 1.0에 접근합니다 (적어도 단순한 사인파를 혼합 할 때 시도했을 때). 이 추천서를 여러 곳에서 읽었지만 겸손한 견해로는 쓸모없는 접근입니다.

파도가 '혼합'할 때 물리적으로 일어나는 일은 여기에 이미 제안 된 많은 포스터와 마찬가지로 절단 장치가 추가된다는 것입니다. 어느 한 쪽

  • 클립 (결과도 왜곡됨) 또는
  • 16 비트 값을 32 비트 숫자로 요약 한 다음 소스 수로 나눕니다 (왜곡을 피하는 유일한 방법이므로 제가 제안하는 것입니다)

저는 한 번 이렇게했습니다. floats (-1과 1 사이의 샘플)를 사용하고 값이 1 인 "autoGain"변수를 초기화했습니다. 그런 다음 모든 샘플을 함께 추가합니다 (2 이상일 수도 있음). 그런 다음 나가는 신호에 autoGain을 곱합니다. 곱하기 전 신호 합의 절대 값이 1보다 크면 1 /이 합계 값을 할당합니다. 이것은 효과적으로 autogain을 1보다 작게 만들고 0.7이라고 가정하면 전체 사운드가 너무 커진다는 것을 알게 되 자마자 일부 운영자가 메인 볼륨을 빠르게 낮추는 것과 같습니다. 그런 다음 조정 가능한 시간 동안 autogain에 추가하여 마침내 "1"이 될 때까지 추가합니다 (우리 오퍼레이터가 충격에서 회복되어 천천히 볼륨을 높이고 있습니다 :-)).


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

귀하의 프로필은 임베디드 시스템에서 작업한다고 말했기 때문에 부동 소수점 연산이 항상 옵션은 아니라고 가정합니다.

> 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?

예. 볼륨의 절반은 다소 주관적이지만 여기에서 볼 수있는 것은 볼륨 (라우드니스)을 절반으로 줄이면 약 10dB (파워를 10으로 나누거나 샘플 값을 3.16으로 나눈 값)이 감소하는 것입니다. 그러나 당신은 분명히 샘플 값 을 절반 으로 낮추는 것을 의미 합니다. 이것은 6dB 감소, 눈에 띄는 감소하지만, 꽤 많은 양을 절반으로 줄인다로 (음량 테이블 매우 유용합니다).

이 6dB 감소로 모든 클리핑을 방지 할 수 있습니다. 하지만 더 많은 입력 채널을 원하면 어떻게 될까요? 4 개 채널의 경우 입력 값을 4로 나눠야합니다. 즉, 12dB만큼 낮아져 각 채널의 크기가 절반으로 줄어 듭니다.

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

클립이 아닌 믹스를 원하며 입력 신호의 음량을 잃지 않습니다. 이것은 어떤 종류의 왜곡 없이는 불가능합니다.

Mark Ransom이 제안한대로, 채널당 6dB만큼 손실되지 않으면 서 클리핑을 방지하는 솔루션은 "추가 및 클리핑"과 "평균화"사이의 어딘가에 도달하는 것입니다.

이는 두 가지 소스를위한 것입니다. 더하기, 1과 2 사이의 어딘가로 나누기 (범위를 [-65536, 65534]에서 더 작은 것으로 줄임), 제한합니다.

이 솔루션으로 자주 클립하고 너무 거칠게 들리면 컴프레서로 리미트 니를 부드럽게 할 수 있습니다. 입력 전력에 따라 분할 계수를 만들어야하므로 좀 더 복잡합니다. 먼저 리미터를 단독으로 시도하고 결과가 만족스럽지 않은 경우에만 압축기를 고려하십시오.


나는 다음 일을했다 :

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

아이디어를 공유 해주셔서 감사합니다. 최근에는 사운드 믹싱 관련 작업도하고 있습니다. 나는 또한이 문제에 대한 실험을 마쳤습니다.

iOS RemoteIO AudioUnit에서 8Khz 샘플 속도 및 16 비트 샘플 (SInt16) 사운드를 사용하고 있습니다.

내 실험에서 내가 찾은 최고의 결과는이 모든 답변과 다른 것이었지만 기본은 동일합니다 ( Rody가 제안한 것처럼 )

" 함께 추가해야하지만 오버플로 / 언더 플로를 방지하기 위해 결과를 허용 범위로 잘라 냅니다."

그러나 오버플로 / 언더 플로없이 추가하는 가장 좋은 방법은 무엇입니까?

핵심 아이디어 :: A와 B라고하는 두 개의 음파가 있고 그 결과로 생성 된 C는 두 개의 A와 B 파동 중첩 됩니다. 제한된 비트 범위에서 샘플이 오버플로 될 수 있습니다. 따라서 이제 우리는 중첩 파형 의 상향 및 하향에서 최소 한계 교차 에서 최대 한계 교차계산할 수 있습니다 . 이제 중첩 파형의 상단 부분에 대한 최대 상향 한계 교차빼고 중첩 파형 의 하단에 최소 하향 제한 교차 를 더합니다. VOILA ... 끝났습니다.

단계 :

  1. 먼저 상한 교차 최대 값 및 하한 교차의 최소 값대해 데이터 루프를 한 번 통과하십시오 .
  2. Make another traversal to the audio data, subtract the maximum value from the positive audio data portion and add minimum value to the negative portion of audio data.

the following code would show the implementation.

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

it works fine for me, i have later intention gradually change the value of upSideDownValue & downSideUpValue to gain a smoother output.


This question is old but here is the valid method IMO.

  1. Convert both sample in power.
  2. Add both sample in power.
  3. Normalize it. Such as the maximum value doesn't go over your limit.
  4. Convert back in amplitude.

You can make the first 2 steps together, but will need the maximum and minimum to normalize in a second pass for step 3 and 4.

I hope it helps someone.


나는 그들을 함께 추가한다고 말하고 싶습니다. 16 비트 PCM 공간이 넘치면 사용중인 사운드가 이미 엄청나게 크므로이를 감쇠해야합니다. 이로 인해 자체적으로 너무 부드러워지는 경우 OS 설정 또는 스피커의 노브를 돌리는 등 전체 볼륨 출력을 높이는 다른 방법을 찾으십시오.

참조 URL : https://stackoverflow.com/questions/376036/algorithm-to-mix-sound

반응형