Métodos de compresión/almacenamiento de datos multimedia en formatos WAVE y JPEG, parte 1

¡Hola! Mi primera serie de artículos estará dedicada al estudio de métodos de compresión y almacenamiento de imágenes / sonido, como JPEG (imagen) y WAVE (sonido), y también incluirá ejemplos de programas utilizando estos formatos (.jpg, .wav) en la práctica. En esta parte, nos centraremos en WAVE.

Historia

WAVE (Waveform Audio File Format) es un formato de archivo contenedor para almacenar grabaciones de flujo de audio. Este contenedor generalmente se utiliza para almacenar audio sin comprimir en modulación por impulsos codificados. (Tomado de Wikipedia)

Fue inventado y publicado en 1991 junto con RIFF por las empresas Microsoft e IBM (las principales compañías de TI de la época).

Estructura del archivo

El archivo tiene una parte de encabezado, los datos en sí, pero no hay pie de página. El encabezado pesa en total 44 bytes.
En el encabezado se encuentran configuraciones sobre el número de bits por muestra, la frecuencia de muestreo, la profundidad del sonido y otra información necesaria para la tarjeta de sonido. (Todos los valores numéricos de la tabla deben escribirse en orden Little-Endian)

Nombre del bloque
Tamaño del bloque (B)
Descripción / Propósito
Valor (en algunos está fijado

chunkId
4
Definición del archivo como contenedor multimedia
0x52494646 en Big-Endian («RIFF»)

chunkSize
4
Tamaño total del archivo sin chunkId y chunkSize
FILE_SIZE — 8

format
4
Definición del tipo de RIFF
0x57415645 en Big-Endian («WAVE»)

subchunk1Id
4
Para que el archivo ocupe más espacio, continuación del formato
0x666d7420 en Big-Endian («fmt «)

subchunk1Size
4
Encabezado restante (en bytes)
16 por defecto (para el caso sin compresión del flujo de audio)

audioFormat
2
Formato de audio (depende del método de compresión y la estructura de los datos de audio)
1 (para PCM, que es lo que estamos considerando)

numChannels
2
Número de canales
1 / 2, tomaremos 1 canal (3 / 4 / 5 / 6 / 7… — pista de audio específica, por ejemplo 4 para sonido cuadrado, etc.)

sampleRate
4
Frecuencia de muestreo de sonido (en Hertz)
Cuanto mayor, mejor será la calidad del sonido, pero necesitará más memoria para crear una pista de audio de la misma longitud, el valor recomendado es 48000 (calidad de sonido más aceptable)

byteRate
4
Número de bytes por 1 segundo
sampleRate numChannels bitsPerSample (siguiente)

blockAlign
2
Número de bytes por 1 muestra
numChannels * bitsPerSample: 8

bitsPerSample
2
Número de bits por 1 muestra (profundidad)
Cualquier número múltiplo de 8. Cuanto más grande, mejor y más pesado será el audio, a partir de 32 bits no hay diferencia para el humano

subchunk2Id
4
Etiqueta de inicio de datos (ya que puede haber otros elementos en el encabezado dependiendo del audioFormat)
0x64617461 en Big-Endian («data»)

subchunk2Size
4
Tamaño del área de datos
tamaño de data en int

data
byteRate * duración del audio
Datos de audio
?

Ejemplo con WAVE

La tabla anterior se puede convertir fácilmente en una estructura en C, pero nuestro lenguaje hoy es Python. Lo más sencillo que se puede hacer usando "onda" es un generador de ruido. Para esta tarea no necesitaremos un byteRate alto ni compresión.
Para empezar, importemos los módulos necesarios:

# WAV.py

from struct import pack  # перевод py-объектов в базовые типы из C
from os import urandom  # функция для чтения /dev/urandom, для windows:
# from random import randint
# urandom = lambda sz: bytes([randint(0, 255) for _ in range(sz)])  # лямбда под windows, т.к. urandom'а в винде нет
from sys import argv, exit  # аргументы к проге и выход

if len(argv) != 3:  # +1 имя скрипта (-1, если будете замораживать)
    print('Usage: python3 WAV.py [num of samples] [output]')
    exit(1)

A continuación, necesitamos crear todas las variables necesarias de la tabla según sus tamaños. Las magnitudes variables aquí dependen solo de numSamples (número de muestras). Cuantas más sean, más tiempo durará nuestro ruido.

numSamples = int(argv[1])
output_path = argv[2]

chunkId = b'RIFF'
Format = b'WAVE'
subchunk1ID = b'fmt '
subchunk1Size = b'x10x00x00x00'  # 0d16
audioFormat = b'x01x00'
numChannels = b'x02x00'  # 2 canales serán suficientes (estéreo)
sampleRate = pack('<L', 1000)  # 1000 es suficiente, pero si se pone más, el ruido se escuchará mejor. Con 1000 suena como el viento
bitsPerSample = b'x20x00'  # 0d32
byteRate = pack('<L', 1000 * 2 * 4)  # sampleRate * numChannels * bitsPerSample / 8  (sonido de 32 bits)
blockAlign = b'x08x00'  # numChannels * BPS / 8
subchunk2ID = b'data'
subchunk2Size = pack('<L', numSamples * 2 * 4)  # * numChannels * BPS / 8
chunkSize = pack('<L', 36 + numSamples * 2 * 4)  # 36 + subchunk2Size

data = urandom(1000 * 2 * 4 * numSamples)  # el ruido en sí

Solo queda escribirlos en la secuencia necesaria (como en la tabla):

with open(output_path, 'wb') as fh:
    fh.write(chunkId + chunkSize + Format + subchunk1ID +
            subchunk1Size + audioFormat + numChannels + 
            sampleRate + byteRate + blockAlign + bitsPerSample +
            subchunk2ID + subchunk2Size + data)  # escribimos

Y así, está listo. Para usar el script, necesitamos agregar los argumentos necesarios de la línea de comandos:
python3 WAV.py [número de muestras] [salida]
número de muestras — col. de muestras
salida — ruta al archivo de salida

Aquí hay un enlace a un archivo de audio de prueba con ruido, pero para ahorrar memoria disminuí el BPS a 1b/s y reduje el número de canales a 1 (con un flujo de audio estéreo sin comprimir de 32 bits a 64kbs resultó en un archivo .wav limpio de 80M, así que ahora solo son 10): https://instaud.io/3Dcy

Todo el código completo (WAV.py) (El código tiene muchas repeticiones de valores de variables, esto es solo un bosquejo):

from struct import pack  # conversión de objetos py a tipos básicos de C
from os import urandom  # función para leer /dev/urandom, para windows:
# from random import randint
# urandom = lambda sz: bytes([randint(0, 255) for _ in range(sz)])  # lambda para windows, ya que no hay urandom en windows
from sys import argv, exit  # argumentos para el programa y salida

if len(argv) != 3:  # +1 nombre del script (-1 si van a congelar)
    print('Uso: python3 WAV.py [num de muestras] [salida]')
    exit(1)

numSamples = int(argv[1])
output_path = argv[2]

chunkId = b'RIFF'
Format = b'WAVE'
subchunk1ID = b'fmt '
subchunk1Size = b'x10x00x00x00'  # 0d16
audioFormat = b'x01x00'
numChannels = b'x02x00'  # 2 canales son suficientes (estéreo) 
sampleRate = pack('<L', 1000)  # 1000 es suficiente, pero se puede más alto.
bitsPerSample = b'x20x00'  # 0d32
byteRate = pack('<L', 1000 * 2 * 4)  # sampleRate * numChannels * bitsPerSample / 8  (sonido de 32 bits)
blockAlign = b'x08x00'  # numChannels * BPS / 8
subchunk2ID = b'data'
subchunk2Size = pack('<L', numSamples * 2 * 4)  # * numChannels * BPS / 8
chunkSize = pack('<L', 36 + numSamples * 2 * 4)  # 36 + subchunk2Size

data = urandom(1000 * 2 * 4 * numSamples)  # el ruido mismo

with open(output_path, 'wb') as fh:
    fh.write(chunkId + chunkSize + Format + subchunk1ID +
            subchunk1Size + audioFormat + numChannels + 
            sampleRate + byteRate + blockAlign + bitsPerSample +
            subchunk2ID + subchunk2Size + data)  # escribe el resultado en el archivo

Summary

Ahora ya sabes un poco más sobre el sonido digital y cómo se almacena. En esta publicación no utilizamos compresión (audioFormat), pero para revisar cada uno de los formatos populares serán necesarias unas 10 publicaciones. Espero que hayas aprendido algo nuevo y que esto te ayude en tus futuros desarrollos.
¡Gracias!

Fuentes

Estructura del archivo WAV
WAV — Wikipedia

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster