Здравствуйте! Моя первая серия статей будет направлена на изучение методов сжатия и хранения изображений/звука, таких как 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 |
format | 4 | Определение типа из RIFF | 0x57415645 в Big-Endian ("WAVE") |
subchunk1Id | 4 | 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'е |
data | 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'\x10\x00\x00\x00' # 0d16
audioFormat = b'\x01\x00'
numChannels = b'\x02\x00' # 2-х каналов будет достаточно (стерео)
sampleRate = pack('<L', 1000) # 1000 хватит, но если поставить больше, то шум будет слышен лучше. С 1000-ю он звучит, как ветер
bitsPerSample = b'\x20\x00' # 0d32
byteRate = pack('<L', 1000 * 2 * 4) # sampleRate * numChannels * bitsPerSample / 8 (32 bit sound)
blockAlign = b'\x08\x00' # 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'\x10\x00\x00\x00' # 0d16
audioFormat = b'\x01\x00'
numChannels = b'\x02\x00' # 2-х каналов будет достаточно (стерео)
sampleRate = pack('<L', 1000) # 1000 хватит, но можно и больше.
bitsPerSample = b'\x20\x00' # 0d32
byteRate = pack('<L', 1000 * 2 * 4) # sampleRate * numChannels * bitsPerSample / 8 (32 bit sound)
blockAlign = b'\x08\x00' # 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. Надеюсь вы узнали что-то новое для себя и это вам поможет в будущих разработках.
Спасибо!
kovserg
А чем штатный wave не угодил?