Метады сціску/захоўвання медыя даных у фарматах 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'е

gegevens
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

Дадаць каментар