Bună! Prima mea serie de articole va fi dedicată studiului metodelor de comprimare și stocare a imaginilor și sunetului, cum ar fi JPEG (imagine) și WAVE (sunet), inclusiv exemple de programe care folosesc aceste formate (.jpg, .wav) în practică. În această parte, ne vom concentra pe WAVE.
Istoria
WAVE (Waveform Audio File Format) este un format de fișier-container pentru stocarea înregistrărilor fluxurilor audio. Acest container este, de obicei, utilizat pentru stocarea sunetului necomprimat în modulație impulsivă-codificată. (Preluat din Wikipedia)
A fost inventat și publicat în 1991 împreună cu RIFF de către companiile Microsoft și IBM (principalele companii IT ale vremii).
Structura fișierului
Fișierul are o parte de antet, datele propriu-zise, dar nu are un footer. Antetul cântărește în total 44 de bytes.
În antet se află setările pentru numărul de biți pe eșantion, frecvența de desincronizare, adâncimea sunetului și alte informații necesare pentru placa de sunet. (Toate valorile numerice din tabel trebuie să fie scrise în ordine Little-Endian)
Numele blocului
Dimensiunea blocului (B)
Descriere/Scop
Valoarea (pentru unii este fixă)
chunkId
4
Definirea fișierului ca mediu-container
0x52494646 în Big-Endian („RIFF”)
chunkSize
4
Dimensiunea întregului fișier fără chunkId și chunkSize
FILE_SIZE — 8
format
4
Definirea tipului din RIFF
0x57415645 în Big-Endian („WAVE”)
subchunk1Id
4
Pentru ca fișierul să ocupe mai mult spațiu continuarea formatului
0x666d7420 în Big-Endian („fmt “)
subchunk1Size
4
Antetul rămas (în bytes)
16 implicit (în cazul fără compresie a fluxului audio)
audioFormat
2
Format audio (depinde de metoda de comprimare și structura datelor audio)
1 (pentru PCM, pe care îl discutăm)
numChannels
2
Numărul de canale
1/2, vom lua 1 canal (3/4/5/6/7... — o pistă audio specifică, de exemplu 4 pentru sunet quad etc.)
sampleRate
4
Frecvența de eșantionare a sunetului (în Hertz)
Cu cât este mai mare, cu atât sunetul va fi mai bun, dar va necesita mai mult spațiu pentru a crea o pistă audio de aceeași lungime, valoarea recomandată este de 48000 (calitate audio cea mai acceptabilă)
byteRate
4
Numărul de bytes pe secundă
sampleRate numChannels bitsPerSample (următorul)
blockAlign
2
Numărul de bytes pentru 1 eșantion
numChannels * bitsPerSample: 8
bitsPerSample
2
Numărul de biți pe 1 eșantion (adâncime)
Orice număr, multiplu de 8. Cu cât este mai mare, cu atât audio va fi mai bun și mai greu, de la 32 biți nu există diferență pentru om.
subchunk2Id
4
Marcajul de început al datelor (deoarece pot fi și alte elemente de antet în funcție de audioFormat)
0x64617461 în Big-Endian («data»)
subchunk2Size
4
Dimensiunea zonei de date
dimensiunea data în int
data
byteRate * durata audio
Date audio
?
Exemplu cu WAVE
Tabelul anterior poate fi ușor tradus într-o structură în C, dar limbajul nostru de astăzi este Python. Cea mai simplă lucru pe care îl putem face cu „valul” – generator de zgomot. Pentru această sarcină nu avem nevoie de un byteRate ridicat și de compresie.
Pentru început, să importăm modulele necesare:
# 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)Apoi, trebuie să creăm toate variabilele necesare din tabel în funcție de dimensiunile lor. Mărimile variabile depind doar de numSamples (numărul de mostre). Cu cât vor fi mai multe, cu atât zgomotul va dura mai mult.
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 canale sunt suficiente (stereo)
sampleRate = pack('<L', 1000) # 1000 ar fi suficient, dar dacă pui mai mult, zgomotul va fi mai clar. La 1000 sună ca vântul
bitsPerSample = b'x20x00' # 0d32
byteRate = pack('<L', 1000 * 2 * 4) # sampleRate * numChannels * bitsPerSample / 8 (sunet de 32 bit)
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) # zgomotul în sineRămâne doar să le scriem în secvența necesară (așa cum este în tabel):
with open(output_path, 'wb') as fh:
fh.write(chunkId + chunkSize + Format + subchunk1ID +
subchunk1Size + audioFormat + numChannels +
sampleRate + byteRate + blockAlign + bitsPerSample +
subchunk2ID + subchunk2Size + data) # scriemȘi așa, este gata. Pentru a utiliza scriptul, trebuie să adăugăm argumentele necesare pentru linia de comandă:
python3 WAV.py [număr de mostre] [ieșire]
număr de mostre — numărul de mostre
ieșire — calea către fișierul de ieșire
Iată un link către un fișier audio de test cu zgomot, dar pentru a economisi memorie am redus BPS la 1b/s și am redus numărul canalelor la 1 (cu un flux audio stereo necompresat de 32 de biți la 64kbs a rezultat un fișier de 80M .wav, dar așa doar 10):
Tot codul (WAV.py) (Codul conține multe duplicări ale valorilor variabile, acesta este doar un schiță):
from struct import pack # conversia obiectelor py în tipuri de bază din C
from os import urandom # funcția pentru citirea /dev/urandom, pentru windows:
# from random import randint
# urandom = lambda sz: bytes([randint(0, 255) for _ in range(sz)]) # lambda pentru windows, deoarece nu există urandom în windows
from sys import argv, exit # argumente pentru program și ieșire
if len(argv) != 3: # +1 numele scriptului (-1, dacă doriți să înghețați)
print('Utilizare: python3 WAV.py [număr de mostre] [ieșire]')
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 canale sunt suficiente (stereo)
sampleRate = pack('<L', 1000) # 1000 va fi suficient, dar se poate și mai mult.
bitsPerSample = b'x20x00' # 0d32
byteRate = pack('<L', 1000 * 2 * 4) # sampleRate * numChannels * bitsPerSample / 8 (sunet de 32 biți)
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) # zgomotul propriu-zis
with open(output_path, 'wb') as fh:
fh.write(chunkId + chunkSize + Format + subchunk1ID +
subchunk1Size + audioFormat + numChannels +
sampleRate + byteRate + blockAlign + bitsPerSample +
subchunk2ID + subchunk2Size + data) # scriem rezultatul în fișierRezultatul
Iată, ați aflat puțin mai multe despre sunetul digital și despre cum este stocat. În această postare nu am folosit compresia (audioFormat), dar pentru a discuta fiecare dintre cele populare va fi nevoie de 10 articole. Sper că ați învățat ceva nou și că vă va ajuta în viitoarele dezvoltări.
Mulțumesc!
Surse
Sursa: habr.com
