Cześć! Moja pierwsza seria artykułów będzie skierowana na naukę metod kompresji i przechowywania obrazów/dźwięku, takich jak JPEG (obraz) i WAVE (dźwięk); będą też przykłady programów z wykorzystaniem tych formatów (.jpg, .wav) w praktyce. W tej części omówimy WAVE.
Historia
WAVE (Waveform Audio File Format) to format pliku-kontenera do przechowywania nagrania strumienia audio. Ten kontener jest zazwyczaj używany do przechowywania nieskompresowanego dźwięku w modulacji impulsywno-kodowej. (Pobrano z Wikipedii)
Został wynaleziony i opublikowany w 1991 roku wraz z RIFF przez firmy Microsoft i IBM (Główne firmy IT tamtego czasu).
Struktura pliku
Plik ma część nagłówkową, same dane, ale brak stopki. Nagłówek ma w sumie 44 bajty.
W nagłówku znajdują się ustawienia liczby bitów w próbce, częstotliwości próbkowania, głębokości dźwięku i innych informacji potrzebnych dla karty dźwiękowej. (Wszystkie wartości liczbowe w tabeli powinny być zapisane w porządku Little-Endian)
Nazwa bloku
Rozmiar bloku (B)
Opis/Przeznaczenie
Wartość (niektóre mają sztywną wartość)
chunkId
4
Określenie pliku jako kontenera multimedialnego
0x52494646 w Big-Endian ("RIFF")
chunkSize
4
Rozmiar całego pliku bez chunkId i chunkSize
FILE_SIZE — 8
format
4
Określenie typu z RIFF
0x57415645 w Big-Endian ("WAVE")
subchunk1Id
4
Aby plik zajmował więcej miejsca, ciag dalszy formatu
0x666d7420 w Big-Endian ("fmt ")
subchunk1Size
4
Pozostała część nagłówka (w bajtach)
Domyślnie 16 (w przypadku braku kompresji strumienia audio)
audioFormat
2
Format audio (zależy od metody kompresji i struktury danych audio)
1 (dla PCM, który tu rozważamy)
numChannels
2
Liczba kanałów
1/2, weźmiemy 1 kanał (3/4/5/6/7… — specyficzna ścieżka audio, na przykład 4 dla dźwięku quad itp.)
sampleRate
4
Częstotliwość próbkowania dźwięku (w Hertzach)
Im wyższa, tym lepsza jakość dźwięku, ale tym więcej pamięci potrzeba do stworzenia ścieżki audio o tej samej długości, zalecana wartość to 48000 (najbardziej akceptowalna jakość dźwięku)
byteRate
4
Liczba bajtów na 1 sekundę
sampleRate numChannels bitsPerSample (dalej)
blockAlign
2
Liczba bajtów dla 1 próbki
numChannels * bitsPerSample: 8
bitsPerSample
2
Liczba bitów na 1 próbkę (głębokość)
Każda liczba będąca wielokrotnością 8. Im więcej, tym lepiej i ciężej będzie audio, od 32 bitów różnicy nie ma dla człowieka.
subchunk2Id
4
Etykieta dla początku danych (ponieważ mogą być inne elementy nagłówka w zależności od audioFormat)
0x64617461 w Big-Endian ("data")
subchunk2Size
4
Rozmiar obszaru danych
rozmiar data w int’ie
data
byteRate * długość audio
Dane audio
?
Przykład z WAVE
Poprzednią tabelę można łatwo przetłumaczyć na strukturę w C, ale naszym językiem na dziś jest Python. Najłatwiej jest wykorzystać „falę” — generator szumów. Do tego zadania nie potrzebujemy wysokiego byteRate i kompresji.
Na początek zaimportujemy niezbędne moduły:
# 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)Następnie musimy utworzyć wszystkie niezbędne zmienne z tabeli według ich rozmiarów. Zmienne nieustalone w niej zależą tylko od numSamples (liczba próbek). Im więcej ich będzie, tym dłużej będzie trwał nasz szum.
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' # wystarczą 2 kanały (stereo)
sampleRate = pack('<L', 1000) # 1000 wystarczy, ale jeśli ustawisz więcej, szum będzie lepiej słyszalny. Przy 1000 brzmi jak wiatr
bitsPerSample = b'x20x00' # 0d32
byteRate = pack('<L', 1000 * 2 * 4) # sampleRate * numChannels * bitsPerSample / 8 (32-bitowy dźwięk)
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) # sam szumPozostaje tylko zapisać je w odpowiedniej kolejności (jak w tabeli):
with open(output_path, 'wb') as fh:
fh.write(chunkId + chunkSize + Format + subchunk1ID +
subchunk1Size + audioFormat + numChannels +
sampleRate + byteRate + blockAlign + bitsPerSample +
subchunk2ID + subchunk2Size + data) # zapisujemyI tak, gotowe. Aby użyć skryptu, musimy dodać niezbędne argumenty wiersza poleceń:
python3 WAV.py [liczba próbek] [wyjście]
liczba próbek — ilość próbek
wyjście — ścieżka do pliku wyjściowego
Oto link do testowego pliku audio z szumem, ale aby zaoszczędzić pamięć, obniżyłem BPS do 1b/s i liczbę kanałów do 1 (z 32-bitowym nieskompresowanym dźwiękiem stereo w 64kbs uzyskałem 80M czystego pliku .wav, a w ten sposób tylko 10):
Cały kod w całości (WAV.py) (Kod zawiera wiele powtórzeń wartości zmiennych, to tylko szkic):
from struct import pack # konwersja obiektów py na podstawowe typy z C
from os import urandom # funkcja do odczytu \/dev\/urandom, dla windows:
# from random import randint
# urandom = lambda sz: bytes([randint(0, 255) for _ in range(sz)]) # lambda dla windows, ponieważ urandom w win nie istnieje
from sys import argv, exit # argumenty do programu i wyjście
if len(argv) != 3: # +1 nazwa skryptu (-1, jeśli planujesz zamrozić)
print('Użycie: python3 WAV.py [liczba próbek] [ścieżka wyjściowa]')
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 kanały będą wystarczające (stereo)
sampleRate = pack('<L', 1000) # 1000 wystarczy, ale można i więcej.
bitsPerSample = b'x20x00' # 0d32
byteRate = pack('<L', 1000 * 2 * 4) # sampleRate * numChannels * bitsPerSample \/ 8 (32 bit dźwięk)
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) # sam szum
with open(output_path, 'wb') as fh:
fh.write(chunkId + chunkSize + Format + subchunk1ID +
subchunk1Size + audioFormat + numChannels +
sampleRate + byteRate + blockAlign + bitsPerSample +
subchunk2ID + subchunk2Size + data) # zapisujemy wynik do plikuPodsumowanie
Teraz dowiedziałeś się nieco więcej o dźwięku cyfrowym i o tym, jak jest przechowywany. W tym wpisie nie używaliśmy kompresji (audioFormat), ale omówienie każdego z popularnych wymagałoby 10 artykułów. Mam nadzieję, że nauczyłeś się czegoś nowego i to pomoże ci w przyszłych projektach.
Dziękuję!
Źródła
Źródło: habr.com
