我喜欢考虑一切如何,并用数字表示.例如,明文由类似ASCII的代码表示,图像由RGB值表示.这些是表示文本和图像的最简单方法.
用数字表示音频的最简单方法是什么?我想学习如何编写使用音频的程序,并认为这将是一个很好的开始方式.不过,我似乎无法在互联网上找到任何好的解释.
der*_*ert 92
物理上,正如您可能知道的那样,音频是一种振动.通常,我们谈论的是大约20Hz到20,000Hz之间的空气振动.这意味着空气每秒来回移动20至20,000次.
如果您测量振动并将其转换为电信号(例如,使用麦克风),您将获得电信号,其电压与声音的波形相同.在我们的纯音假设中,该波形将与正弦函数的波形匹配.
现在,我们有一个模拟信号,即电压.仍然不是数字.但是,我们知道这个电压在(例如)-1V和+ 1V之间变化.当然,我们可以将电压表连接到电线并读取电压.
任意地,我们将改变电压表的比例.我们将电压乘以32767.它现在称为-1V -32767和+ 1V 32767.哦,它会四舍五入到最接近的整数.
现在,我们将电压表连接到计算机,并指示计算机每秒读取电表449次.添加第二个电压表(用于另一个立体声通道),我们现在可以获得音频CD上的数据.
这种格式称为立体声44,100 Hz,16位线性PCM.它实际上只是一堆电压测量.
音频可以由数字样本表示.实质上,采样器(也称为模数转换器)每1/fs抓取一个音频信号的值,其中fs是采样频率.然后,ADC对信号进行量化,这是一种舍入操作.因此,如果您的信号范围为0到3伏(满量程范围),则样本将四舍五入为,例如16位数.在此示例中,每1/fs /一次记录一个16位数字
因此,例如,大多数WAV/MP3被采样为44kHz的音频信号.我不知道你想要多少细节,但是有一种称为"奈奎斯特采样率"的东西说采样频率必须至少是所需频率的两倍.因此,在您的WAV/MP3文件中,您最多可以听到22 kHz的频率.
您可以在这个区域进行很多细节.最简单的形式肯定是WAV格式.它是未压缩的音频.像mp3和ogg这样的格式必须先解压缩才能使用它们.
最小C音频生成示例
以下示例以原始格式生成纯1000k Hz的正弦曲线.采样率为44.1kHz,持续4秒.
main.c中:
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(void) {
FILE *f;
const double PI2 = 2 * acos(-1.0);
const double SAMPLE_FREQ = 44100;
const unsigned int NSAMPLES = 4 * SAMPLE_FREQ;
uint16_t ampl;
uint8_t bytes[2];
unsigned int t;
f = fopen("out.raw", "wb");
for (t = 0; t < NSAMPLES; ++t) {
ampl = UINT16_MAX * 0.5 * (1.0 + sin(PI2 * t * 1000.0 / SAMPLE_FREQ));
bytes[0] = ampl >> 8;
bytes[1] = ampl & 0xFF;
fwrite(bytes, 2, sizeof(uint8_t), f);
}
fclose(f);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
生成out.raw:
gcc -std=c99 -o main main.c -lm
./main
Run Code Online (Sandbox Code Playgroud)
out.raw直接玩:
sudo apt-get install ffmpeg
ffplay -autoexit -f u16be -ar 44100 -ac 1 out.raw
Run Code Online (Sandbox Code Playgroud)
或转换为更常见的音频格式,然后使用更常见的音频播放器:
ffmpeg -f u16be -ar 44100 -ac 1 -i out.raw out.flac
vlc out.flac
Run Code Online (Sandbox Code Playgroud)
参数解释于:https://superuser.com/a/1063230/128124
这是D synth中更有趣的佳能:以编程方式合成编程音乐?
在Ubuntu 18.04上测试过.GitHub上游.
物理
音频被编码为每个时刻的单个数字.将其与视频进行比较,视频每时刻需要WIDTH*HEIGHT数字.
然后将此数字转换为扬声器振膜的线性位移:
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef uint16_t point_type_t;
double PI2;
void write_ampl(FILE *f, point_type_t ampl) {
uint8_t bytes[2];
bytes[0] = ampl >> 8;
bytes[1] = ampl & 0xFF;
fwrite(bytes, 2, sizeof(uint8_t), f);
}
/* https://en.wikipedia.org/wiki/Piano_key_frequencies */
double piano_freq(unsigned int i) {
return 440.0 * pow(2, (i - 49.0) / 12.0);
}
/* Chord formed by the nth note of the piano. */
point_type_t piano_sum(unsigned int max_ampl, unsigned int time,
double sample_freq, unsigned int nargs, unsigned int *notes) {
unsigned int i;
double sum = 0;
for (i = 0 ; i < nargs; ++i)
sum += sin(PI2 * time * piano_freq(notes[i]) / sample_freq);
return max_ampl * 0.5 * (nargs + sum) / nargs;
}
enum notes {
A0 = 1, AS0, B0,
C1, C1S, D1, D1S, E1, F1, F1S, G1, G1S, A1, A1S, B1,
C2, C2S, D2, D2S, E2, F2, F2S, G2, G2S, A2, A2S, B2,
C3, C3S, D3, D3S, E3, F3, F3S, G3, G3S, A3, A3S, B3,
C4, C4S, D4, D4S, E4, F4, F4S, G4, G4S, A4, A4S, B4,
C5, C5S, D5, D5S, E5, F5, F5S, G5, G5S, A5, A5S, B5,
C6, C6S, D6, D6S, E6, F6, F6S, G6, G6S, A6, A6S, B6,
C7, C7S, D7, D7S, E7, F7, F7S, G7, G7S, A7, A7S, B7,
C8,
};
int main(void) {
FILE *f;
PI2 = 2 * acos(-1.0);
const double SAMPLE_FREQ = 44100;
point_type_t ampl;
point_type_t max_ampl = UINT16_MAX;
unsigned int t, i;
unsigned int samples_per_unit = SAMPLE_FREQ * 0.375;
unsigned int *ip[] = {
(unsigned int[]){4, 2, C3, E4},
(unsigned int[]){4, 2, G3, D4},
(unsigned int[]){4, 2, A3, C4},
(unsigned int[]){4, 2, E3, B3},
(unsigned int[]){4, 2, F3, A3},
(unsigned int[]){4, 2, C3, G3},
(unsigned int[]){4, 2, F3, A3},
(unsigned int[]){4, 2, G3, B3},
(unsigned int[]){4, 3, C3, G4, E5},
(unsigned int[]){4, 3, G3, B4, D5},
(unsigned int[]){4, 2, A3, C5},
(unsigned int[]){4, 3, E3, G4, B4},
(unsigned int[]){4, 3, F3, C4, A4},
(unsigned int[]){4, 3, C3, G4, G4},
(unsigned int[]){4, 3, F3, F4, A4},
(unsigned int[]){4, 3, G3, D4, B4},
(unsigned int[]){2, 3, C4, E4, C5},
(unsigned int[]){2, 3, C4, E4, C5},
(unsigned int[]){2, 3, G3, D4, D5},
(unsigned int[]){2, 3, G3, D4, B4},
(unsigned int[]){2, 3, A3, C4, C5},
(unsigned int[]){2, 3, A3, C4, E5},
(unsigned int[]){2, 2, E3, G5},
(unsigned int[]){2, 2, E3, G4},
(unsigned int[]){2, 3, F3, A3, A4},
(unsigned int[]){2, 3, F3, A3, F4},
(unsigned int[]){2, 3, C3, E4},
(unsigned int[]){2, 3, C3, G4},
(unsigned int[]){2, 3, F3, A3, F4},
(unsigned int[]){2, 3, F3, A3, C5},
(unsigned int[]){2, 3, G3, B3, B4},
(unsigned int[]){2, 3, G3, B3, G4},
(unsigned int[]){2, 3, C4, E4, C5},
(unsigned int[]){1, 3, C4, E4, E5},
(unsigned int[]){1, 3, C4, E4, G5},
(unsigned int[]){1, 2, G3, G5},
(unsigned int[]){1, 2, G3, A5},
(unsigned int[]){1, 2, G3, G5},
(unsigned int[]){1, 2, G3, F5},
(unsigned int[]){3, 3, A3, C4, E5},
(unsigned int[]){1, 3, A3, C4, E5},
(unsigned int[]){1, 3, E3, G3, E5},
(unsigned int[]){1, 3, E3, G3, F5},
(unsigned int[]){1, 3, E3, G3, E5},
(unsigned int[]){1, 3, E3, G3, D5},
};
f = fopen("canon.raw", "wb");
for (i = 0; i < sizeof(ip) / sizeof(int*); ++i) {
unsigned int *cur = ip[i];
unsigned int total = samples_per_unit * cur[0];
for (t = 0; t < total; ++t) {
ampl = piano_sum(max_ampl, t, SAMPLE_FREQ, cur[1], &cur[2]);
write_ampl(f, ampl);
}
}
fclose(f);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
位移推动空气向后和向前推动,产生压力差,通过空气作为P波传播.
只有位移很重要:一个恒定的信号,即使是最大的,也不会产生声音:振膜只是停留在一个固定的位置.
该采样频率决定了位移的速度应该做的.
44,1kHz是一种常见的采样频率,因为人类可以听到高达20kHz的频率,并且由于奈奎斯特 - 香农采样定理.
采样频率类似于视频的FPS,尽管它与我们通常看到的视频的25(电影院) - 144(硬核游戏监视器)范围相比具有更高的价值.
格式
.raw 是一种未指定的格式,仅包含幅度字节,而不包含元数据.
我们必须在命令行上传递一些元数据参数,如采样频率,因为格式不包含该数据.
还有其他未压缩格式包含所有需要的元数据,例如.wav,参见:从头开始的WAV文件合成 - C.
然而,在实践中,大多数人专门处理压缩格式,这使得文件/流媒体变得更小.其中一些格式考虑了人耳的特征,以有损方式进一步压缩音频.
生物学
人类主要通过频率分解(AKA 傅里叶变换)来感知声音.
我认为这是因为内耳有不同频率共振的部分(TODO确认).
因此,在合成音乐时,我们更多地考虑增加频率而不是时间点.这个例子说明了这一点.
这导致考虑每个时间点在20Hz和20kHz之间的1D向量.
数学傅里叶变换失去了时间概念,因此我们在合成时所做的是采用点组,并总结该组的频率,并在那里进行傅里叶变换.
幸运的是,傅立叶变换是线性的,所以我们可以直接加上和标准化位移.
每组点的大小导致时间 - 频率精度权衡,由与海森堡不确定性原理相同的数学介导.
小波可以是对该中间时间 - 频率描述的更精确的数学描述.
将声音表示为数字的最简单方法是 PCM(脉冲编码调制)。这意味着以设定的频率记录声音的振幅(每个振幅值称为一个样本)。例如,CD 质量的声音是频率为 44100 Hz 的 16 位样本(立体声)。
样本可以表示为整数(通常为 8、12、16、24 或 32 位)或浮点数(16 位浮点数或 32 位双精度数)。该数字可以有符号或无符号。
对于 16 位有符号样本,值 0 将位于中间,-32768 和 32767 将是最大振幅。对于 16 位无符号样本,值 32768 将位于中间,而 0 和 65535 将是最大幅度。
对于浮点样本,通常的格式是 0 在中间,-1.0 和 1.0 是最大幅度。
然后可以压缩 PCM 数据,例如使用 MP3。
| 归档时间: |
|
| 查看次数: |
20344 次 |
| 最近记录: |