Algoritmo para mezclar el sonido

Tengo dos flujos de sonido en bruto que necesito agregar juntos. A los fines de esta pregunta, podemos suponer que tienen la misma tasa de bits y la misma profundidad de bits (por ejemplo, muestra de 16 bits, frecuencia de muestreo de 44.1khz).

Obviamente, si los agrego juntos, desbordaré y desbordaré mi espacio de 16 bits. Si los agrego juntos y los divido entre dos, el volumen de cada uno se reduce a la mitad, lo cual no es correcto en cuanto a sonido: si dos personas hablan en una habitación, sus voces no se silencian a la mitad, y un micrófono puede elegirlas ambos sin tocar el limitador.

  • Entonces, ¿cuál es el método correcto para agregar estos sonidos en mi mezclador de software?
  • ¿Estoy equivocado y el método correcto es bajar el volumen de cada uno a la mitad?
  • ¿Debo agregar un compresor / limitador o alguna otra etapa de procesamiento para obtener el volumen y el efecto de mezcla que estoy intentando?

-Adán

Debe agregarlos juntos, pero recorte el resultado al rango permitido para evitar el exceso / defecto.

En el caso de que ocurra el recorte, introducirás distorsión en el audio, pero eso es inevitable. Puede usar su código de recorte para “detectar” esta condición e informar al usuario / operador (equivalente a la luz roja de “clip” en un mezclador …)

Podría implementar un compresor / limitador más “adecuado”, pero sin conocer su aplicación exacta, es difícil decir si valdría la pena.

Si está haciendo un montón de procesamiento de audio, es posible que desee representar sus niveles de audio como valores de punto flotante, y solo volver al espacio de 16 bits al final del proceso. Los sistemas de audio digital de alta gama a menudo funcionan de esta manera.

Hay un artículo sobre mezclar aquí . Me interesaría saber qué piensan los demás acerca de esto.

Preferiría hacer un comentario sobre una de las dos respuestas mejor clasificadas, pero debido a mi escasa reputación (supongo) no puedo.

La respuesta “marcada”: sume y el clip es correcto, pero no si desea evitar el recorte.

La respuesta con el enlace comienza con un algoritmo vudú viable para dos señales positivas en [0,1], pero luego aplica un álgebra muy defectuosa para derivar un algoritmo completamente incorrecto para los valores firmados y los valores de 8 bits. El algoritmo tampoco escala a tres o más entradas (el producto de las señales disminuirá mientras que la sum aumenta).

Entonces, convierta las señales de entrada en flotantes, escalelas a [0,1] (p. Ej., Se convertiría en un valor de 16 bits con signo).
float v = ( s + 32767.0 ) / 65536.0 (close enough...))
y luego sumrlos.

Para escalar las señales de entrada, probablemente debas hacer un trabajo real en lugar de multiplicar o restar un valor vudú. Sugeriría mantener un volumen promedio continuo y luego, si comienza a derivar alto (por encima de 0,25 decir) o bajo (por debajo de 0,01 decir) comenzar a aplicar un valor de escala basado en el volumen. Esto se convierte esencialmente en una implementación de nivel automática, y escala con cualquier cantidad de entradas. Lo mejor de todo es que en la mayoría de los casos no interferirá con tu señal.

La mayoría de las aplicaciones de mezcla de audio harán su mezcla con números de punto flotante (32 bits es lo suficientemente bueno como para mezclar una pequeña cantidad de flujos). Traduzca las muestras de 16 bits en números de punto flotante con el rango -1.0 a 1.0 representando la escala completa en el mundo de 16 bits. Luego, sume las muestras juntas; ahora tiene mucho margen de maniobra. Finalmente, si termina con cualquier muestra cuyo valor exceda la escala completa, puede atenuar toda la señal o usar limitación estricta (valores de recorte a 1.0).

Esto proporcionará resultados de sonido mucho mejores que el agregado de muestras de 16 bits y el desbordamiento. Aquí hay un ejemplo de código muy simple que muestra cómo puede sumr dos muestras de 16 bits juntas:

 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) 

“Más tranquilo a la mitad” no es del todo correcto. Debido a la respuesta logarítmica de la oreja, dividir las muestras por la mitad hará que sea 6-db más silencioso, ciertamente notable, pero no desastroso.

Es posible que desee comprometerse multiplicando por 0,75. Eso lo hará 3 dB más silencioso, pero reducirá la posibilidad de desbordamiento y también disminuirá la distorsión cuando ocurra.

No puedo creer que nadie sepa la respuesta correcta. Todos están lo suficientemente cerca, pero aún así, una filosofía pura. El más cercano, es decir, el mejor fue: (s1 + s2) – (s1 * s2). Es un enfoque excelente, especialmente para MCU.

Entonces, el algoritmo va:

  1. Averigua el volumen en el que quieres que esté el sonido de salida. Puede ser el promedio o el máximo de una de las señales.
    factor = average(s1) Asume que ambas señales ya están correctas , sin desbordar el 32767.0
  2. Normaliza ambas señales con este factor:
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  3. Agréguelos y normalice el resultado con el mismo factor
    output = ((s1+s2)/max(s1+s2))*factor

Tenga en cuenta que después del paso 1. en realidad no necesita volver a números enteros, puede trabajar con flotantes en un intervalo de -1.0 a 1.0 y aplicar el retorno a enteros al final con el factor de potencia elegido previamente. Espero no haberme equivocado ahora, porque estoy apurado.

También puede comprarse algo de margen con un algoritmo como y = 1.1x – 0.2x ^ 3 para la curva, y con un tope en la parte superior e inferior. Lo usé en Hexaphone cuando el jugador está tocando múltiples notas juntas (hasta 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; } } 

No es a prueba de balas, pero le permitirá alcanzar un nivel de 1.25 y suaviza el clip a una curva agradable. Produce distorsión armónica, que suena mejor que recorte y puede ser deseable en algunas circunstancias.

Si necesita hacer esto bien, sugeriría buscar implementaciones de software de código abierto, al menos para la teoría.

Algunos enlaces:

Audacia

GStreamer

En realidad, probablemente deberías estar usando una biblioteca.

convierta las muestras a valores de coma flotante que van desde -1.0 a +1.0, luego:

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

Tienes razón sobre agregarlos juntos. Siempre puede escanear la sum de los dos archivos en busca de puntos máximos, y escalar todo el archivo si alcanzan algún tipo de umbral (o si el promedio del mismo y sus puntos circundantes alcanzan un umbral)

Creo que, siempre que las transmisiones no estén correlacionadas, no deberías tener demasiado de qué preocuparte, deberías ser capaz de salir adelante con la saturación. Si realmente te preocupa la distorsión en los puntos de clip, un limitador suave probablemente funcionaría bien.

convierta las muestras a valores de coma flotante que van desde -1.0 a +1.0, luego:

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

Introducirá una gran distorsión cuando | s1 + s2 | aproximación 1.0 (al menos cuando lo intenté al mezclar ondas sinusoidales simples). Leí esta recomendación en varios lugares, pero en mi humilde opinión, es un enfoque inútil.

Lo que ocurre físicamente cuando las ondas se “mezclan” es que sus amplificadores se agregan, al igual que muchos de los carteles aquí sugeridos. Ya sea

  • clip (distorsiona el resultado también) o
  • Resum sus valores de 16 bits en un número de 32 bits, y luego divida por el número de sus fonts (eso es lo que sugeriría, ya que es la única forma que conozco de evitar las distorsiones)

Lo hice de esta manera una vez: utilicé flotadores (muestras entre -1 y 1), y inicié una variable “autoGain” con un valor de 1. Luego, agregaría todas las muestras juntas (también podría haber más de 2). Entonces multiplicaría la señal saliente con autoGain. Si el valor absoluto de la sum de las señales antes de la multiplicación fuera superior a 1, asignaría 1 / este valor de sum. Esto efectivamente haría que el autogain sea más pequeño que 1 digamos 0.7 y sería equivalente a que un operador baje rápidamente el volumen principal tan pronto como vea que el sonido en general se está poniendo demasiado fuerte. Luego, durante un período de tiempo ajustable, boostía la ganancia automática hasta que finalmente regrese a “1” (nuestro operador se ha recuperado de la descarga y está subiendo lentamente el volumen :-)).

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

Como su perfil dice que trabaja en sistemas integrados, asumiré que las operaciones de punto flotante no siempre son una opción.

 > So what's the correct method to add these sounds together in my software mixer? 

Como habrás adivinado, agregar y recortar es la forma correcta de hacerlo si no quieres perder volumen en las fonts. Con muestras que son int16_t , necesita que la sum sea int32_t , luego limite y convierta nuevamente a int16_t .

 > Am I wrong and the correct method is to lower the volume of each by half? 

Sí. Reducir a la mitad el volumen es algo subjetivo, pero lo que se puede ver aquí y allá es que la reducción del volumen (volumen) es una disminución de alrededor de 10 dB (dividiendo la potencia por 10, o los valores de muestra por 3.16). Pero quiere decir, obviamente, reducir los valores de muestra a la mitad. Esto es una disminución de 6 dB, una reducción notable, pero no tanto como la mitad del volumen (la tabla de volumen es muy útil).

Con esta reducción de 6 dB, evitará todos los clipping. Pero, ¿qué sucede cuando quieres más canales de entrada? Para cuatro canales, deberá dividir los valores de entrada entre 4, que disminuyen en 12 dB, disminuyendo así la mitad del volumen para cada canal.

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

Desea mezclar, no cortar, y no perder volumen en las señales de entrada. Esto no es posible, no sin algún tipo de distorsión.

Como sugiere Mark Ransom, una solución para evitar el recorte sin perder hasta 6 dB por canal es golpear en algún lugar entre “agregar y recortar” y “promediar”.

Eso es para dos fonts: agregar, dividir por algún lugar entre 1 y 2 (reducir el rango de [-65536, 65534] a algo más pequeño), luego limitar.

Si a menudo se corta con esta solución y suena demasiado fuerte, entonces es posible que desee suavizar la rodilla límite con un compresor. Esto es un poco más complejo, ya que necesita hacer que el factor de división dependa de la potencia de entrada. Primero pruebe el limitador solo y considere el compresor solo si no está satisfecho con el resultado.

Hice lo siguiente:

 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 

Multiplique el margen superior izquierdo de src por el valor de destino normalizado MAX_VAL y añádalo. Nunca se recortará, nunca será menos ruidoso y sonará absolutamente natural.

Ejemplo:

 250.5882 = (((255 - 180) * 240) / 255) + 180 

Y esto suena bien 🙂

Encontré una nueva forma de agregar muestras de una manera en la que nunca pueden exceder un rango determinado. La Idea básica es convertir valores en un rango entre -1 a 1 a un rango entre aproximadamente -Infinito a + Infinito, sumr todo e invertir la transformación inicial. Se me ocurrieron las siguientes fórmulas para esto:

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

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

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

Lo probé y funciona, pero para múltiples sonidos fuertes, el audio resultante suena peor que simplemente agregar las muestras juntas y recortar cada valor que es demasiado grande. Usé el siguiente código para probar esto:

 #include  #include  #include  #include  #include  #include  #include  #include  // fabs wasn't accurate enough long double ldabs(long double x){ return x < 0 ? -x : x; } // -Inf 1. ) ret = 1.; return ret; } // -1< =input<=+1, -Inf 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 max_read ) max_read = read_count; } long double insamples[argc-2]; for( size_t j=0; j 

Gracias a todos por compartir sus ideas, recientemente también estoy haciendo un trabajo relacionado con la mezcla de sonido. También he hecho algo de experimentación sobre este tema, puede ayudarlos chicos :).

Tenga en cuenta que estoy usando la frecuencia de muestreo de 8Khz y el sonido de muestra de 16 bit (SInt16) en ios Remote AudioUnit.

A lo largo de mis experimentos, el mejor resultado que encontré fue algo diferente de toda esta respuesta, pero el básico es el mismo (como sugiere Roddy )

Debe agregarlos juntos, pero recortar el resultado al rango permitido para evitar el exceso / defecto “.

Pero, ¿cuál debería ser la mejor forma de agregar sin desbordamiento / subdesbordamiento?

Idea clave :: Usted tiene dos ondas de sonido, digamos A y B, y la onda resultante C será la superposición de dos ondas A y B. La muestra en un rango de bits limitado puede hacer que se desborde. Entonces ahora podemos calcular el límite máximo cruzado en el cruce de límite máximo y mínimo en la parte inferior de la forma de onda de superposición. Ahora restaremos la cruz del límite máximo de la parte superior a la parte superior de la forma de onda de superposición y agregaremos el límite mínimo de reducción transversal a la parte inferior de la forma de onda de superposición. VOILA … has terminado.

Pasos:

  1. Primero recorra el bucle de datos una vez para obtener el valor máximo de la cruz del límite superior y el valor mínimo de la cruz del límite inferior.
  2. Realice otro recorrido a los datos de audio, reste el valor máximo de la porción de datos de audio positiva y agregue el valor mínimo a la parte negativa de los datos de audio.

el siguiente código mostraría la implementación.

 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 0) { OutputData[i] = summedValue - upSideDownValue; } else { OutputData[i] = summedValue; } } return OutputData; } 

funciona bien para mí, luego tengo intención de cambiar gradualmente el valor de upSideDownValue y downSideUpValue para obtener un resultado más suave.

Yo diría que simplemente agrégalos juntos. Si está desbordando su espacio de PCM de 16 bits, entonces los sonidos que está usando ya son increíblemente altos para empezar y debería atenuarlos. Si eso los hiciera demasiado suaves, busque otra forma de boost la salida de volumen general, como una configuración de sistema operativo o girar la perilla de los altavoces.