Métodos de compressão/armazenamento de dados de mídia nos formatos WAVE e JPEG, parte 1

Olá! Minha primeira série de artigos será dedicada ao estudo de métodos de compressão e armazenamento de imagens/som, como JPEG (imagem) e WAVE (som), incluindo exemplos de programas usando esses formatos (.jpg, .wav) na prática. Nesta parte, vamos nos concentrar no WAVE.

Histórico

WAVE (Waveform Audio File Format) é um formato de arquivo contêiner para armazenar gravações de fluxo de áudio. Este contêiner é normalmente usado para armazenar som não compactado em modulação por Pulse Code. (Retirado da Wikipedia)

Foi criado e publicado em 1991 junto com RIFF pelas empresas Microsoft e IBM (As principais empresas de TI da época).

A estrutura do arquivo

O arquivo possui uma parte de cabeçalho, os dados em si, mas não há rodapé. O cabeçalho ocupa 44 bytes no total.
No cabeçalho estão as configurações do número de bits por amostra, taxa de amostragem, profundidade do som e outras informações necessárias para a placa de som. (Todos os valores numéricos da tabela devem ser gravados na ordem Little-Endian)

Nome do bloco
Tamanho do bloco (B)
Descrição/Propósito
Valor (alguns são fixos)

chunkId
4
Define o arquivo como um contêiner de mídia
0x52494646 em Big-Endian («RIFF»)

chunkSize
4
Tamanho total do arquivo sem chunkId e chunkSize
FILE_SIZE — 8

format
4
Definição do tipo a partir de RIFF
0x57415645 em Big-Endian («WAVE»)

subchunk1Id
4
Para que o arquivo ocupe mais espaço, continuação do formato
0x666d7420 em Big-Endian («fmt «)

subchunk1Size
4
Cabeçalho restante (em bytes)
16 por padrão (para o caso sem compressão do fluxo de áudio)

audioFormat
2
Formato de áudio (depende do método de compressão e da estrutura dos dados de áudio)
1 (para PCM, que é o que estamos analisando)

numChannels
2
Número de canais
1/2, nós pegaremos 1 canal (3/4/5/6/7… — trilha de áudio específica, por exemplo, 4 para som quadrafônico, etc.)

sampleRate
4
Taxa de amostragem do som (em Hertz)
Quanto maior, melhor será a qualidade do som, mas mais memória será necessária para criar uma trilha de áudio da mesma duração, o valor recomendado é 48000 (qualidade de som mais aceitável)

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

blockAlign
2
Número de bytes para 1 amostra
numChannels * bitsPerSample: 8

bitsPerSample
2
Número de bits por amostra (profundidade)
Qualquer número múltiplo de 8. Quanto maior, melhor e mais pesado será o áudio, a partir de 32 bits não há diferença perceptível para o ser humano

subchunk2Id
4
Marcador de início dos dados (pois podem existir outros elementos do cabeçalho dependendo do audioFormat)
0x64617461 em Big-Endian («data»)

subchunk2Size
4
Tamanho da área de dados
tamanho data em int

data
byteRate * duração do áudio
Dados de áudio
?

Exemplo com WAVE

A tabela anterior pode ser facilmente traduzida para a estrutura em C, mas a nossa linguagem hoje é Python. A coisa mais simples que podemos fazer usando a "onda" é gerar ruído. Para essa tarefa, não precisamos de um alto byteRate e compressão.
Primeiro, vamos importar os módulos necessários:

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

Em seguida, precisamos criar todas as variáveis necessárias da tabela com base em seus tamanhos. As variáveis não constantes aqui dependem apenas de numSamples (número de amostras). Quanto mais amostras tivermos, mais tempo levará para gerar nosso ruído.

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 canais são suficientes (estéreo)
sampleRate = pack('<L', 1000)  # 1000 é o suficiente, mas se aumentarmos, o ruído será mais audível. Com 1000, soa como vento
bitsPerSample = b'x20x00'  # 0d32
byteRate = pack('<L', 1000 * 2 * 4)  # sampleRate * numChannels * bitsPerSample / 8  (soma de som 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)  # o próprio ruído

Só resta gravar tudo na sequência necessária (como na tabela):

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

E assim, está pronto. Para usar o script, precisamos adicionar os argumentos necessários da linha de comando:
python3 WAV.py [num de amostras] [saída]
num de amostras — número de amostras
saída — caminho para o arquivo de saída

Aqui está um link para um arquivo de áudio de teste com ruído, mas para economizar memória, reduzi o BPS para 1b/s e o número de canais para 1 (com um fluxo de áudio estéreo não comprimido de 32 bits a 64kbs resultou em 80M de um arquivo .wav puro, mas agora é apenas 10): https://instaud.io/3Dcy

Todo o código completo (WAV.py) (O código contém muitas duplicações de valores de variáveis, é apenas um esboço):

from struct import pack  # conversão de objetos py para tipos básicos de C
from os import urandom  # função para ler /dev/urandom, para windows:
# from random import randint
# urandom = lambda sz: bytes([randint(0, 255) for _ in range(sz)])  # lambda para windows, pois não há urandom no Windows
from sys import argv, exit  # argumentos do programa e saída

if len(argv) != 3:  # +1 nome do script (-1, se você congelar)
    print('Uso: python3 WAV.py [número de amostras] [saída]')
    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 canais são suficientes (estéreo)
sampleRate = pack('<L', 1000)  # 1000 é suficiente, mas pode ser mais.
bitsPerSample = b'x20x00'  # 0d32
byteRate = pack('<L', 1000 * 2 * 4)  # sampleRate * numChannels * bitsPerSample / 8  (som 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)  # o ruído em si

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

Resultado

Agora você aprendeu um pouco mais sobre som digital e como ele é armazenado. Neste post, não utilizamos compressão (audioFormat), mas para discutir cada um dos formatos populares serão necessárias cerca de 10 matérias. Espero que você tenha aprendido algo novo e que isso ajude em seus futuros desenvolvimentos.
Obrigado!

Fontes

Estrutura do arquivo WAV
WAV — Wikipédia

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster