你好! 我的第一个系列文章将重点研究图像/音频压缩和存储方法,例如 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):
整个代码(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篇文章。我希望你能学到一些新的东西,这对你将来的开发有帮助。
谢谢大家!
来源
来源: habr.com