Вітаю! Моя перша серія статей буде спрямована на вивчення методів стиснення та зберігання зображень/звуку, таких як 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):
Весь код повністю (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. Сподіваюся ви дізналися щось нове для себе і це допоможе вам у майбутніх розробках.
Спасибо!
Джерела
Джерело: habr.com