以 WAVE 和 JPEG 格式压缩/存储媒体数据的方法,第 1 部分

你好! 我的第一个系列文章将重点研究图像/音频压缩和存储方法,例如 JPEG(图像)和 WAVE(声音),并且还将包括在实践中使用这些格式(.jpg、.wav)的程序示例。 在这一部分中,我们将看看 WAVE。

故事

WAVE(波形音频文件格式)是一种用于存储音频流录音的容器文件格式。 该容器通常用于存储未压缩的脉冲编码调制音频。 (摘自维基百科)

它是由微软和IBM(当时领先的IT公司)于1991年与RIFF一起发明并发布的。

文件结构

该文件有一个页眉部分、数据本身,但没有页脚。 标头总共有 44 个字节。
标头包含样本位数、采样率、声音深度等的设置。 声卡所需的信息。 (所有数值表值必须以Little-Endian顺序写入)

块名称
块尺寸(B)
描述/目的
值(对于某些来说它是固定的

块ID
4
将文件定义为媒体容器
0x52494646(大端字节序)(“RIFF”)

块大小
4
不含 chunkId 和 chunkSize 的整个文件的大小
文件大小 - 8

格式
4
RIFF 的类型定义
大端字节序(“WAVE”)中的 0x57415645

子块1Id
4
使文件占用更多的空间作为格式的延续
0x666d7420 大端字节序(“fmt”)

子块1尺寸
4
剩余标头(以字节为单位)
默认16(没有音频流压缩的情况)

音频格式
2
音频格式(取决于压缩方法和音频数据结构)
1(对于PCM,这是我们正在考虑的)

频道数
2
通道数
1/2,我们将采用 1 个通道(3/4/5/6/7... - 特定音轨,例如 4 表示四声道等)

采样率
4
音频采样率(以赫兹为单位)
越高,声音越好,但创建相同长度的音轨需要更多内存,建议值为48000(最能接受的音质)

字节率
4
每秒字节数
采样率 频道数 每个样本的位数(进一步)

块对齐
2
1 个样本的字节数
通道数 * 每个样本位数:8

每个样本位数
2
每 1 个样本的位数(深度)
任何 8 的倍数的数字。数字越大,音频越好、越重;从 32 位开始,对人类来说没有区别

子块2Id
4
数据引用标记(因为可能有其他标头元素,具体取决于音频格式)
Big-Endian 中的 0x64617461(“数据”)

子块2尺寸
4
数据区大小
int 数据大小

data
字节率 * 音频持续时间
音频数据
?

波示例

上一个表可以很容易地翻译成 C 语言的结构,但我们今天的语言是 Python。 您能做的最简单的事情就是使用“波”——噪声发生器。 对于此任务,我们不需要高字节率和压缩。
首先,让我们导入必要的模块:

# 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]
样本数 - 计数。 样品
输出 — 输出文件的路径

这里是一个带有噪音的测试音频文件的链接,但为了节省内存,我将 BPS 降低到 1b/s,并将通道数降低到 1(使用 32kbs 的 64 位未压缩立体声音频流,结果是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

添加评论