Metody kompresji/przechowywania danych multimedialnych w formatach WAVE i JPEG, część 1

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 szum

Pozostaje 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)  # zapisujemy

I 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): https://instaud.io/3Dcy

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 pliku

Podsumowanie

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

Struktura pliku WAV
WAV — Wikipedia

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster