Like my articles? Feel free to vote for me as ML Writer of the year . El manejo de datos de audio es una tarea esencial para los ingenieros de aprendizaje automático que trabajan en los campos del análisis de voz, la recuperación de información musical y el análisis de datos multimodales, pero también para los desarrolladores que simplemente desean editar, grabar y transcodificar sonidos. Este artículo muestra los conceptos básicos del manejo de datos de audio mediante herramientas de línea de comandos y también proporciona una inmersión no tan profunda en el manejo de sonidos en Python. Entonces, ¿qué es el sonido y cuáles son sus atributos básicos? Según la física, el sonido es una vibración viajera, es decir, una onda que se desplaza a través de un medio como el aire. La onda de sonido transfiere energía de partícula a partícula hasta que finalmente es "recibida" por nuestros oídos y percibida por nuestro cerebro. Los dos atributos básicos del sonido son la (lo que también llamamos volumen) y (una medida de las vibraciones de la onda por unidad de tiempo). amplitud la frecuencia Foto de Kai Dahms en Unsplash Al igual que las imágenes y los videos, el sonido es una señal analógica que debe transformarse en una señal digital para almacenarse en computadoras y analizarse mediante software. Esta conversión de analógico a digital incluye dos procesos: y . muestreo cuantificación se utiliza para convertir la señal continua variable en el tiempo x(t) en una secuencia discreta de números reales x(n). El intervalo entre dos muestras discretas sucesivas es el período de muestreo (Ts). Usamos la frecuencia de muestreo (fs = 1/Ts) como el atributo que describe el proceso de muestreo. El muestreo Las frecuencias de muestreo típicas son 8 KHz, 16 KHz y 44,1 KHz. 1 Hz significa una muestra por segundo, por lo que, obviamente, las frecuencias de muestreo más altas significan más muestras por segundo y, por lo tanto, una mejor calidad de la señal. (En realidad, esto significa que la señal discreta puede capturar un rango más alto de frecuencias, es decir, de 0 a fs/2 Hz según la regla de Nyquist) La es el proceso de reemplazar cada número real, x(n), de la secuencia de muestras con una de un conjunto finito de valores discretos. En otras palabras, la cuantificación es el proceso de reducir la precisión del número infinito de una muestra de audio a una precisión finita definida por un número particular de bits. cuantificación aproximación En la mayoría de los casos, se utilizan 16 bits por muestra para representar cada muestra cuantificada, lo que significa que hay 2¹⁶ niveles para la señal cuantificada. Por esa razón, los valores de audio sin procesar generalmente varían de -2¹⁵ a 2¹⁵ (se usa 1 bit para el signo), sin embargo, como veremos más adelante, esto generalmente se normaliza en el rango (-1, 1) por simplicidad. Normalmente llamamos a esta propiedad de resolución de bits del procedimiento de cuantificación "resolución de muestra" y se mide en . bits por muestra Herramientas y bibliotecas utilizadas en este artículo He seleccionado las siguientes herramientas de línea de comandos, programas y bibliotecas para usar en el manejo básico de datos de audio: /libav. FFmpeg ( ) es un proyecto gratuito de código abierto para manejar archivos y transmisiones multimedia. Algunos piensan que ffmpeg y libav son lo mismo, pero en realidad libav es un proyecto bifurcado de ffmpeg ffmpeg https://ffmpeg.org ( ), también conocida como “la navaja suiza de los programas de procesamiento de sonido”, es una utilidad de línea de comandos multiplataforma gratuita para el procesamiento básico de audio. A pesar de que no se ha actualizado desde 2015, sigue siendo una buena solución. En este artículo demostramos principalmente ffmpeg y un par de ejemplos en sox sox http://sox.sourceforge.net ) es un programa gratuito, de código abierto y multiplataforma para editar sonidos audacity ( https://www.audacityteam.org : usaremos ( ) y scipy ( ) para leer datos de audio y ( ). programación pydub https://github.com/jiaaro/pydub https://scipy-cookbook.readthedocs.io librosa https://librosa.github.io /librosa/ También podríamos usar ( ) para IO o para extracción de funciones y análisis de señales más avanzados. pyAudioAnalysis https://github.com/tyiannak/pyAudioAnalysis Finalmente, también usaremos ( ) para la visualización básica de señales. plotly https://plotly.com Este artículo está dividido en dos partes: 1ra parte: cómo usar ffmpeg y sox para manejar archivos de audio 2da parte: cómo manejar archivos de audio mediante programación y realizar un procesamiento básico Parte I: Manejo de datos de audio: la forma de la línea de comandos A continuación, se muestran algunos ejemplos del manejo de audio más básico, como la conversión entre formatos, el recorte temporal, la fusión y la segmentación, utilizando principalmente ffmpeg y sox. Para convertir (mkv) (mp3) video a audio ffmpeg -i video.mkv audio.mp3 Para reducir la a 16 KHz, convertir estéreo (2 canales) y convertir MP3 muestras de audio sin comprimir), es necesario usar las propiedades -ar (velocidad de audio) -ac (canal de audio): resolución a mono (1 canal) a WAV ( ffmpeg -i audio.wav -ar -ac audio_16K_mono.wav 16000 1 Tenga en cuenta que, en ese caso, la conversión de estéreo a mono significa que los dos canales se promedian en uno. Además, la reducción de muestreo de un archivo de audio y la conversión de estéreo a mono se pueden lograr usando de la siguiente manera: sox <source_file_ -r <new_sampling_rate> -c 1 <output_file>) sox Ahora veamos los atributos del nuevo archivo usando ffmpeg: ffmpeg -i audio_16K_mono.wav regresará: Input # , wav, 'audio_16K_mono.wav': Metadata: encoder : Lavf57 Duration: : : , : kb/s Stream # : : Audio: pcm_s16le ([ ][ ][ ][ ] / ), Hz, mono, s16, kb/s 0 from .71 .100 00 03 10.29 bitrate 256 0 0 1 0 0 0 0x0001 16000 256 Para un archivo de audio, por ejemplo, del segundo 60 al 80 (20 segundos de nueva duración): recortar ffmpeg -i audio.wav -ss -t audio_small.wav 60 20 (Esto se puede lograr con el argumento -to, que se usa para definir el final del segmento recortado, en el ejemplo anterior sería 80) Para dos o más archivos de audio, se puede usar el comando "ffmpeg -f concat". Suponga que desea concatenar todos los archivos f1.wav, f2.wav y f3.wav en un archivo grande llamado output.wav. Lo que debe hacer es crear un archivo de texto con el siguiente formato (digamos llamado 'list_of_files_to_concat'): concatenar file file file 'file1.wav' 'file2.wav' 'file3.wav' y luego corre ffmpeg -f concat -i list_of_files_to_concat -c copy output.wav Por otro lado, para un archivo de audio en fragmentos ( ) sucesivos de la (misma) duración especificada se puede hacer con la opción "ffmpeg -f segmento". Por ejemplo, el siguiente comando dividirá output.wav en segmentos de 1 segundo que no se superponen denominados out00000.wav, out00001.wav, etc.: dividir segmentos ffmpeg -i output.wav -f segment -segment_time -c copy out% d.wav 1 05 Con respecto al manejo de canales, además de la simple conversión de mono a estéreo (o de estéreo a mono) a través de la propiedad -ac, es posible que desee (de derecha a izquierda). La forma de lograr esto es a través de la propiedad ffmpeg map_channel: cambiar los canales estéreo ffmpeg -i stereo.wav -map_channel -map_channel stereo_inverted.wav 0.0 .1 0.0 .0 Para crear un , diga left.wav y right.wav: archivo estéreo a partir de dos archivos mono ffmpeg -i left.wav -i right.wav -filter_complex -map mix_channels.wav "[0:a][1:a]join=inputs=2:channel_layout=stereo[a]" "[a]" En la dirección opuesta, para (uno para cada canal): dividir un archivo estéreo en dos mono ffmpeg -i stereo.wav -map_channel left.wav -map_channel right.wav 0.0 .0 0.0 .1 Map_channel también se puede usar para un de una señal estéreo, por ejemplo (abajo, el canal izquierdo está silenciado): silenciar canal ffmpeg -i stereo.wav -map_channel -map_channel muted.wav -1 0.0 .1 La adaptación del también se puede lograr a través de ffmpeg, por ejemplo volumen ffmpeg -i data/music_44100.wav -filter:a “volume= ” data/music_44100_volume_50.wav ffmpeg -i data/music_44100.wav -filter:a “volume= ” data/music_44100_volume_200.wav 0.5 2.0 La siguiente figura presenta una captura de pantalla de la visualización (con Audacity) del original, la adaptación de volumen al 50 % y las señales de adaptación de volumen x2 (200 %). La señal de volumen realzado x2 está claramente (es decir, algunas muestras no se pueden representar y se les asigna el valor máximo permitido: 2¹⁵ para señales de 16 bits): recortada El cambio de también se puede lograr con de la siguiente manera: volumen sox sox -v data/music_44100.wav data/music_44100_volume_50_sox.wav sox -v data/music_44100.wav data/music_44100_volume_200_sox.wav 0.5 2.0 Parte II: Manejo de datos de audio: la forma de programación Cargue archivos WAV y MP3 en la matriz Primero carguemos nuestros datos de audio muestreados en una matriz (usamos matrices numpy ya que se consideran la forma más ampliamente adoptada para procesar secuencias/vectores numéricos). La forma más común de cargar datos WAV en matrices numpy es scipy.io.wavfile, mientras que para datos MP3 se puede usar pydub ( ) que usa ffmpeg para codificar/decodificar datos de audio. numpy https://github.com/jiaaro/pydub En el siguiente ejemplo, la señal almacenada en archivos WAV y MP3 se carga en matrices numpy. misma pydub AudioSegment numpy np scipy.io wavfile plotly.offline init_notebook_mode plotly.graph_objs go plotly fs_wav, data_wav = wavfile.read( ) audiofile = AudioSegment.from_file( ) data_mp3 = np.array(audiofile.get_array_of_samples()) fs_mp3 = audiofile.frame_rate print( . format(((data_mp3 - data_wav)** ).sum())) print( . format(data_wav.shape[ ] / fs_wav)) # Read WAV and MP3 files to array from import import as from import from import import as import # read WAV file using scipy.io.wavfile "data/music_8k.wav" # read MP3 file using pudub "data/music_8k.mp3" 'Sq Error Between mp3 and wav data = {}' 2 'Signal Duration = {} seconds' 0 resultado: Sq Between mp3 and wav data = Signal Duration = seconds Error 0 5.256 Nota: la duración total de la señal cargada (en segundos) se calcula dividiendo el número de muestras por la frecuencia de muestreo (Hz = muestras por segundo). Además, en el ejemplo anterior, calculamos el error cuadrático de la suma para asegurarnos de que las dos señales sean idénticas a pesar de su conversión de mp3 a wav. Señales estéreo Las señales estéreo se manejan a través de arreglos 2D. En el siguiente ejemplo, la matriz data_wav tiene dos columnas, una para cada canal. Por convención, el canal izquierdo es siempre el primero y el segundo el canal derecho. fs_wav, data_wav = wavfile.read( ) time_wav = np.arange( , len(data_wav)) / fs_wav plotly.offline.iplot({ : [go.Scatter(x=time_wav, y=data_wav[:, ], name= ), go.Scatter(x=time_wav, y=data_wav[:, ], name= )]}) # Handling stereo signals "data/stereo_example_small_8k.wav" 0 "data" 0 'left channel' 1 'right channel' Normalización La normalización es necesaria para realizar cálculos en los valores de la señal de audio, ya que hace que los valores de la señal sean independientes de la resolución de la muestra (es decir, las señales con 24 bits por muestra tienen un rango de valores mucho más alto que las señales con 16 bits por muestra). El siguiente ejemplo demuestra cómo normalizar una señal de audio en el rango (-1, 1), simplemente dividiendo por 2¹⁵. Esto se debe a que sabemos que la resolución de la muestra es de 16 bits por muestra. En el raro caso de 24 bits por muestra, esta normalización obviamente debería cambiar respectivamente. fs_wav, data_wav = wavfile.read( ) data_wav_norm = data_wav / ( ** ) time_wav = np.arange( , len(data_wav)) / fs_wav plotly.offline.iplot({ : [go.Scatter(x=time_wav, y=data_wav_norm, name= )]}) # Normalization "data/lost_highway_small.wav" 2 15 0 "data" 'normalized audio signal' Recortar / Segmentar Los siguientes ejemplos muestran cómo obtener los segundos 2 a 4 de la señal previamente cargada y normalizada. Esto se hace simplemente refiriéndose a los índices respectivos en la matriz numpy. Obviamente, los índices deben estar en muestras de audio, por lo que los segundos deben multiplicarse por la frecuencia de muestreo. data_wav_norm_crop = data_wav_norm[ * fs_wav: * fs_wav] time_wav_crop = np.arange( , len(data_wav)) / fs_wav plotly.offline.iplot({ : [go.Scatter(x=time_wav_crop, y=data_wav_norm_crop, name= )]}) # Trim (segment) audio signal (2 seconds) 2 4 0 "data" 'cropped audio signal' Segmentación de tamaño fijo En la primera parte, mostramos cómo podemos segmentar una grabación larga en segmentos que no se superponen usando ffmpeg. El siguiente ejemplo de código muestra cómo hacer lo mismo con Python. La línea 8 realiza la segmentación real en un comando de una sola línea. En general, el siguiente script carga y normaliza una señal de audio, y luego . la divide en segmentos de 1 segundo y escribe cada uno de ellos en un archivo (Preste atención a la nota en el último comentario: deberá convertir a 16 bits antes de guardar en el archivo porque la conversión numpy ha llevado a resoluciones de muestra más altas). fs, signal = wavfile.read( ) signal = signal / ( ** ) signal_len = len(signal) segment_size_t = segment_size = segment_size_t * fs segments = np.array([signal[x:x + segment_size] x np.arange( , signal_len, segment_size)]) iS, s enumerate(segments): wavfile.write( .format(segment_size_t * iS, segment_size_t * (iS + )), fs, (s)) # Fix-sized segmentation (breaks a signal into non-overlapping segments) "data/obama.wav" 2 15 1 # segment size in seconds # segment size in samples # Break signal into list of segments in a single-line Python code for in 0 # Save each segment in a seperate filename for in "data/obama_segment_{0:d}_{1:d}.wav" 1 Un algoritmo simple para eliminar segmentos silenciosos de una grabación El guión anterior ha dividido una grabación en una lista de segmentos de 1 segundo. El siguiente código implementa un método de eliminación de silencio muy simple. Con este fin, calcula la energía como la suma de los cuadrados de las muestras, luego calcula un umbral como el 50 % del valor de la energía mediana y, finalmente, mantiene los segmentos cuya energía está por encima de ese umbral: IPython energies = [(s** ).sum() / len(s) s segments] thres = * np.median(energies) index_of_segments_to_keep = (np.where(energies > thres)[ ]) segments2 = segments[index_of_segments_to_keep] new_signal = np.concatenate(segments2) wavfile.write( , fs, new_signal) plotly.offline.iplot({ : [go.Scatter(y=energies, name= ), go.Scatter(y=np.ones(len(energies)) * thres, name= )]}) IPython.display.display(IPython.display.Audio( )) IPython.display.display(IPython.display.Audio( )) import # Remove pauses using an energy threshold = 50% of the median energy: 2 for in # (attention: integer overflow would occure without normalization here!) 0.5 0 # get segments that have energies higher than a the threshold: # concatenate segments to signal: # and write to file: "data/obama_processed.wav" "data" "energy" "thres" # play the initial and the generated files in notebook: "data/obama.wav" "data/obama_processed.wav" El gráfico de energía/umbral se muestra en la siguiente figura (todos los segmentos cuyas energías están por debajo de la línea roja se eliminan del registro procesado). Además, tenga en cuenta las dos últimas líneas de código (usando la función IPython.display.display()) que se usan para agregar un clip de audio en el que se puede hacer clic directamente en el cuaderno para los archivos de audio iniciales y procesados, como muestra la siguiente captura de pantalla: Puede escuchar las grabaciones originales y procesadas (después de la eliminación del silencio) a continuación: Análisis musical: un ejemplo de juguete sobre estimación de bpm (pulsaciones por minuto) El análisis de música es un dominio de aplicación de procesamiento de señales y aprendizaje automático, que se enfoca en analizar señales musicales, principalmente para la recuperación y recomendación basadas en contenido. Una de las principales tareas en el análisis musical es extraer atributos de alto nivel que describen una canción, como su género musical y el estado de ánimo subyacente. El es uno de los atributos más importantes de una canción. El seguimiento del tempo es la tarea de estimar automáticamente el tempo de una canción (en bpm) directamente desde la señal. Una de las implementaciones básicas del seguimiento de tempo está incluida en la biblioteca . tempo librosa El siguiente ejemplo de juguete toma como entrada un archivo de audio mono donde se almacena una canción y produce un archivo estéreo donde en el canal izquierdo está la canción inicial, mientras que en el canal derecho hay un sonido de "bip" periódico generado artificialmente que "sigue" el tempo principal de la canción: numpy np scipy.io.wavfile wavfile librosa IPython [Fs, s] = wavfile.read( ) tempo, beats = librosa.beat.beat_track(y=s.astype( ), sr=Fs, units= ) beats -= s = s.reshape( , ) s = np.array(np.concatenate((s, np.zeros(s.shape)), axis= )) ib, b enumerate(beats): t = np.arange( , , / Fs) amp_mod = / (np.sqrt(t)+ ) - amp_mod[amp_mod < ] = x = s.max() * np.cos( * np.pi * t * ) * amp_mod s[int(Fs * b): int(Fs * b) + int(x.shape[ ]), ] = x.astype( ) wavfile.write( , Fs, np.int16(s)) IPython.display.display(IPython.display.Audio( )) import as import as import import # load file and extract tempo and beats: 'data/music_44100.wav' 'float' "time" 0.05 # add small 220Hz sounds on the 2nd channel of the song ON EACH BEAT -1 1 1 for in 0 0.2 1.0 0.2 0.2 0.2 0 0 2 220 0 1 'int16' # write a wav file where the 2nd channel has the estimated tempo: "data/music_44100_with_tempo.wav" # play the generated file in notebook: "data/music_44100_with_tempo.wav" El resultado del guión anterior es un archivo WAV donde el canal izquierdo es la canción inicial y el canal derecho es la secuencia de pitidos en los inicios de tempo estimados. A continuación se muestran dos ejemplos de sonidos generados para dos canciones iniciales diferentes: Grabación en tiempo real y análisis de frecuencia Todos los ejemplos de código presentados anteriormente se han centrado principalmente en leer datos de audio de archivos y realizar un procesamiento muy básico en los datos de audio, como recortar o segmentar para ventanas de tamaño fijo, y luego trazar o guardar los sonidos procesados en archivos. El siguiente código va un paso más allá de dos maneras: (a) mostrando cómo un puede el sonido de una manera que permita el procesamiento en línea y en tiempo real (b) introduciendo la representación del dominio de de un sonido. micrófono capturar frecuencia Nuestro objetivo aquí es crear una secuencia de comandos de Python simple que capture el sonido en base a un segmento, y para cada segmento grafica en la terminal la distribución de frecuencia del segmento. La captura de audio en tiempo real se logra a través de la biblioteca . Las muestras de audio se capturan en pequeños segmentos (por ejemplo, de 200 msegundos de duración). Luego, para cada segmento, el código que se presenta a continuación realiza una representación de frecuencia básica ejecutando los siguientes pasos: pyaudio calcular la magnitud de la Transformada Rápida de Fourier (FFT) del segmento registrado. Además, mantenga los valores de frecuencia (en Hz) en una matriz separada, digamos . Entonces, en pocas palabras, de acuerdo con la definición DFT, X freqs X(i) es la energía de la señal de audio que se concentra en frecuencia freqs(i) Hz reduce la muestra de X y frecuencias, de modo que mantenemos muchos menos coeficientes de frecuencia para visualizar el script también calcula la energía del segmento total (no solo la energía en intervalos de frecuencia particulares como se describe en 1). Esto se hace solo para normalizar contra el ancho máximo de la visualización de frecuencia. trace las energías de frecuencia reducidas X para todas las frecuencias (también reducidas) utilizando un gráfico de barras simple. . Estos cuatro pasos se implementan en el siguiente script. El código también está disponible aquí como parte de la biblioteca paura Ver comentarios en línea para una explicación más detallada: numpy np pyaudio struct scipy.fftpack scp termplotlib tpl os rows, columns = os.popen( , ).read().split() buff_size = wanted_num_of_bins = fs = pa = pyaudio.PyAudio() stream = pa.open(format=pyaudio.paInt16, channels= , rate=fs, input= , frames_per_buffer=int(fs * buff_size)) : block = stream.read(int(fs * buff_size)) format = % (len(block) / ) shorts = struct.unpack(format, block) x = np.double(list(shorts)) / ( ** ) seg_len = len(x) energy = np.mean(x ** ) max_energy = max_width_from_energy = int((energy / max_energy) * int(columns)) + max_width_from_energy > int(columns) - : max_width_from_energy = int(columns) - X = np.abs(scp.fft(x))[ :int(seg_len/ )] freqs = (np.arange( , + /len(X), / len(X)) * fs / ) wanted_step = (int(freqs.shape[ ] / wanted_num_of_bins)) freqs2 = freqs[ ::wanted_step].astype( ) X2 = np.mean(X.reshape( , wanted_step), axis= ) fig = tpl.figure() fig.barh(X2, labels=[str(int(f)) + f freqs2[ : ]], show_vals= , max_width=max_width_from_energy) fig.show() print( * (int(rows) - freqs2.shape[ ] - )) # paura_lite: # An ultra-simple command-line audio recorder with real-time # spectrogram visualization import as import import import as import as import # get window's dimensions 'stty size' 'r' 0.2 # window size in seconds 40 # number of frequency bins to display # initialize soundcard for recording: 8000 1 True while 1 # for each recorded window (until ctr+c) is pressed # get current block and convert to list of short ints, "%dh" 2 # then normalize and convert to numpy array: 2 15 # get total energy of the current window and compute a normalization # factor (to be used for visualizing the maximum spectrogram value) 2 0.02 # energy for which the bars are set to max 1 if 10 10 # get the magnitude of the FFT and the corresponding frequencies 0 2 0 1 1.0 1.0 2 # ... and resample to a fix number of frequency bins (to visualize) 0 0 'int' -1 1 # plot (freqs, fft) as horizontal histogram: " Hz" for in 0 -1 False # add exactly as many new lines as they are needed to # fill clear the screen in the next iteration: "\n" 0 1 Y este es un ejemplo de ejecución del script: Todos los ejemplos de código presentados en la parte B están disponibles en este repositorio de github: https://github.com/tyiannak/basic_audio_handling como un cuaderno jupyter. El último ejemplo (el analizador de espectro de línea de comandos en tiempo real) está disponible en https://github.com/tyiannak/paura/blob/master/paura_lite.py Sobre el autor ( tyiannak.github.io ) programa de maestría Thodoris es actualmente el Director de ML en BehavioralSignals.com , donde su trabajo se centra en la construcción de algoritmos que reconocen emociones y comportamientos basados en información de audio. También enseña procesamiento de información multimodal en un de ciencia de datos e inteligencia artificial en Atenas, Grecia.