Методи стиснення/зберігання медіа даних у форматах WAVE та JPEG, частина 1

Вітаю! Моя перша серія статей буде спрямована на вивчення методів стиснення та зберігання зображень/звуку, таких як JPEG (зображення) та WAVE (звук), також у них будуть приклади програм з використанням цих форматів (.jpg, .wav) на практиці. У цій частині ми розглянемо саме WAVE.

Історія

WAVE (Waveform Audio File Format) – формат файлу-контейнера для зберігання запису аудіо потоку. Цей контейнер зазвичай використовується для зберігання стисненого звуку в імпульсно-кодовій модуляції. (Взято з Вікіпедії)

Він був придуманий і опублікований в 1991 разом з RIFF компаніями Microsoft і IBM (Ведучі IT компанії того часу).

Структура файлу

Файл має заголовну частину, самі дані, але немає футера. Заголовок важить загалом 44 байти.
У хедері знаходяться налаштування кількості бітів у семпле, частоти дескритизації, глибини звуку тощо. інформації, яка потрібна для звукової карти. (Всі числові значення таблиці мають бути записані в Little-Endian порядку)

Ім'я блоку
Розмір блоку (B)
Опис/Призначення
Значення (у деяких воно фіксоване

chunkId
4
Визначення файлу як медіаконтейнер
0x52494646 у Big-Endian («RIFF»)

chunkSize
4
Розмір всього файлу без chunkId та chunkSize
FILE_SIZE - 8

формат
4
Визначення типу з RIFF
0x57415645 у Big-Endian («WAVE»)

subchunk1Id
4
Щоб файл більше місця займав продовження format'а
0x666d7420 в Big-Endian ("fmt")

subchunk1Size
4
Хедер, що залишився (в байтах)
16 за умовчанням (для випадку без стиснення аудіопотоку)

audioFormat
2
Аудіо формат (залежить від методу стиснення та структури аудіоданих)
1 (для PCM, який ми розглядаємо)

numChannels
2
кількість каналів
1/2, ми візьмемо 1 канал (3/4/5/6/7… – специфічна аудіодоріжка, наприклад 4 для квадро звуку тощо)

sampleRate
4
Частота семплювання звуку (у Герцах)
Чим більше, тим якіснішим буде звук, але тим більше буде потрібно пам'яті для створення аудіодоріжки тієї ж довжини, рекомендоване значення - 48000 (найбільш прийнятна якість звуку)

byteRate
4
Кількість байт за 1 секунду
sampleRate numChannels bitsPerSample (далі)

blockAlign
2
Кількість байт для 1 семпла
numChannels * bitsPerSample: 8

bitsPerSample
2
Кількість біт за 1 семпл (глибина)
Будь-яке число, кратне 8. Чим більше, тим краще і важче буде аудіо, від 32 біт різниці немає для людини

subchunk2Id
4
Мітка відліку початку даних (бо можуть бути інші елементи хедера залежно від audioFormat)
0x64617461 у Big-Endian («data»)

subchunk2Size
4
Розмір області даних
розмір data в int'і

дані
byteRate * тривалість аудіо
Аудіодані
?

Приклад із WAVE

Попередню таблицю можна легко перевести в структуру на C, але наша мова на сьогодні — Python. Найлегше, що можна зробити, використовуючи "хвилю" - генератор шуму. Для цього завдання нам не знадобиться високий byteRate і стиснення.
Для початку імпортуємо необхідні модулі:

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

Далі нам необхідно створити всі необхідні змінні з таблиці за їх розмірами. Непостійні величини в ній залежать тут тільки від numsamples (кількість семплів). Чим більше їх буде, тим довше буде шум.

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-х каналов будет достаточно (стерео)
sampleRate = pack('<L', 1000)  # 1000 хватит, но если поставить больше, то шум будет слышен лучше. С 1000-ю он звучит, как ветер
bitsPerSample = b'x20x00'  # 0d32
byteRate = pack('<L', 1000 * 2 * 4)  # sampleRate * numChannels * bitsPerSample / 8  (32 bit sound)
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)  # сам шум

Залишилося тільки записати їх у необхідній послідовності (як у таблиці):

with open(output_path, 'wb') as fh:
    fh.write(chunkId + chunkSize + Format + subchunk1ID +
            subchunk1Size + audioFormat + numChannels + 
            sampleRate + byteRate + blockAlign + bitsPerSample +
            subchunk2ID + subchunk2Size + data)  # записываем

І так готово. Для використання скрипту нам потрібно додати необхідні аргументи командного рядка:
python3 WAV.py [num of samples] [output]
num of samples - кільк. семплів
output - шлях до вихідного файлу

Ось посилання на тестовий аудіофайл з шумом, але для економії пам'яті я знизив BPS до 1b/s і кількість каналів опустив до 1 (з 32 бітним стерео аудіопотоком в 64kbs вийшло 80M чистого .wav файлу, а так тільки 10): https://instaud.io/3Dcy

Весь код повністю (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)

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-х каналов будет достаточно (стерео) 
sampleRate = pack('<L', 1000)  # 1000 хватит, но можно и больше.
bitsPerSample = b'x20x00'  # 0d32
byteRate = pack('<L', 1000 * 2 * 4)  # sampleRate * numChannels * bitsPerSample / 8  (32 bit sound)
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)  # сам шум

with open(output_path, 'wb') as fh:
    fh.write(chunkId + chunkSize + Format + subchunk1ID +
            subchunk1Size + audioFormat + numChannels + 
            sampleRate + byteRate + blockAlign + bitsPerSample +
            subchunk2ID + subchunk2Size + data)  # записываем в файл результат

Підсумок

Ось ви й дізналися трохи більше про цифровий звук і про те, як його зберігають. У цьому пості ми не використовували стиснення (audioFormat), але для розгляду кожного з популярних знадобиться статей 10. Сподіваюся ви дізналися щось нове для себе і це допоможе вам у майбутніх розробках.
Спасибо!

Джерела

Структура WAV файлу
WAV - Вікіпедія

Джерело: habr.com

Додати коментар або відгук