我喜欢思考一切如何,并用数字表示。例如,明文由类似ASCII的代码表示,图像由RGB值表示。这些是表示文本和图像的最简单方法。
用数字表示音频的最简单方法是什么?我想学习如何编写与音频一起工作的程序,并认为这是一个很好的开始方式。不过,我似乎无法在互联网上找到任何好的解释。
答案 0 :(得分:89)
在物理上,正如您可能知道的那样,音频是一种振动。通常,我们是 谈论在大约20Hz和20,000Hz之间的空气振动。 这意味着空气每次来回移动20至20,000次 第二
如果您测量该振动并将其转换为电信号 (比如说,使用麦克风),你会得到一个电子信号 电压在与声音相同的波形中变化。在我们的纯音中 假设,该波形将与正弦函数的波形匹配。
现在,我们有一个模拟信号,即电压。仍然不是数字。但我们 知道这个电压在(例如)-1V和+ 1V之间变化。我们可以 当然,将电压表连接到电线并读取电压。
任意地,我们将改变电压表的比例。我们会多次 电压为32767.现在调用-1V -32767 和+ 1V 32767 。哦,还有 它将四舍五入到最接近的整数。
现在,我们将电压表连接到计算机,并指示计算机 每秒读取计数器44,100次。添加第二个电压表(用于 其他立体声通道),我们现在拥有音频上的数据 CD。
此格式称为立体声44,100 Hz,16位线性PCM 。它确实是 只是一堆电压测量。
答案 1 :(得分:8)
音频可以用数字样本表示。实质上,采样器(也称为模数转换器)每1 / fs抓取一个音频信号的值,其中fs是采样频率。然后,ADC对信号进行量化,这是一种舍入操作。因此,如果您的信号范围为0到3伏(满量程范围),则样本将四舍五入为,例如16位数。在此示例中,每1 / fs /
记录一次16位数因此,例如,大多数WAV / MP3采样为44 kHz的音频信号。我不知道你想要多少细节,但是有一种称为“奈奎斯特采样率”的东西说采样频率必须至少是所需频率的两倍。因此,在您的WAV / MP3文件中,您最多可以听到22 kHz的频率。
在这个领域你可以进入很多细节。最简单的形式肯定是WAV格式。它是未压缩的音频。像mp3和ogg这样的格式必须先解压缩才能使用它们。
答案 2 :(得分:7)
最小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;
}
生成out.raw
:
gcc -std=c99 -o main main.c -lm
./main
直接播放out.raw
:
sudo apt-get install ffmpeg
ffplay -autoexit -f u16be -ar 44100 -ac 1 out.raw
或转换为更常见的音频格式,然后使用更常见的音频播放器:
ffmpeg -f u16be -ar 44100 -ac 1 -i out.raw out.flac
vlc out.flac
参数解释于:https://superuser.com/a/1063230/128124
在Ubuntu 18.04上测试。
佳能D中的C
这是一个更有趣的综合例子。
结果:https://www.youtube.com/watch?v=JISozfHATms
的main.c
#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;
}
对于YouTube,我将其准备为:
wget -O canon.png https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/The_C_Programming_Language_logo.svg/564px-The_C_Programming_Language_logo.svg.png
ffmpeg -loop 1 -y -i canon.png -i canon.flac -shortest -acodec copy -vcodec vp9 canon.mkv
这是一个更加面向物理的音频生成视图:How is audio represented with numbers?
在Ubuntu 18.04上测试过。
<强>物理学强>
音频被编码为每个时刻的单个数字。将其与视频进行比较,视频每时刻需要WIDTH * HEIGHT数字。
然后将此数字转换为发言人diaphragm的线性位移:
| /
| /
|-/
| | A I R
|-\
| \
| \
<-> displacement
| /
| /
|---/
| | A I R
|---\
| \
| \
<---> displacement
| /
| /
|-----/
| | A I R
|-----\
| \
| \
<-----> displacement
位移向前和向后推动空气,产生压力差,以P-waves的速度穿过空气。
只有位移很重要:一个恒定的信号,即使是最大的信号,也不会产生声音:振膜只是停留在一个固定的位置。
sampling frequency决定了排位的速度。
44,1kHz是一种常见的采样频率,因为人类可以听到高达20kHz的频率,因为Nyquist–Shannon sampling theorem。
采样频率类似于视频的FPS,但与我们通常用于视频的25(电影院) - 144(硬核游戏监视器)范围相比,它具有更高的价值。
<强>格式强>
.raw
是一种未指定的格式,仅包含幅度字节,而不包含元数据。
我们必须在命令行上传递一些元数据参数,如采样频率,因为格式不包含该数据。
还有其他未压缩格式包含所有需要的元数据,例如.wav
,请参阅:WAV File Synthesis From Scratch - C
然而,在实践中,大多数人只使用压缩格式,这使得文件/流媒体变得更小。其中一些格式考虑了人耳的特征,以便以有损的方式进一步压缩音频。
<强>生物学强>
人类主要通过频率分解来感知声音(AKA Fourier transform)。
我认为这是因为内耳有不同频率共振的部分(TODO确认)。
因此,在合成音乐时,我们更多地考虑增加频率而不是时间点。这说明in this example。
这导致考虑每个时间点在20Hz和20kHz之间的1D向量。
数学傅立叶变换失去了时间概念,因此我们在合成时所做的就是取点组,并总结该组的频率,并在那里进行傅立叶变换。
幸运的是,傅里叶变换是线性的,所以我们可以直接加上和标准化位移。
每组点的大小导致时间 - 频率精度权衡,由与Heisenberg's uncertainty principle相同的数学调解。
Wavelets可能是对此中间时间 - 频率描述的更精确的数学描述。
答案 3 :(得分:4)
将声音表示为数字的最简单方法是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。
答案 4 :(得分:3)
我认为特定采样频率的波形样本将是最基本的表示。
答案 5 :(得分:2)
你有没看过近距离的波形? Y轴简单地表示为整数,通常为16位。
答案 6 :(得分:2)
查找模拟 - 数字转换等内容。这应该让你开始。这些设备可以将音频信号(正弦波)转换为数字表示。因此,一个16位ADC可以表示从-32768到32768之间的正弦。这是定点的。也可以在浮点数中进行(虽然出于性能原因不推荐,但出于范围原因可能需要)。当我们将数字转换为正弦波时,会发生相反的(数字 - 模拟转换)。这由称为DAC的东西处理。
答案 7 :(得分:2)
我认为开始播放音频的好方法是使用Processing和Minim。 该程序将从麦克风中绘制声音的频谱!
import ddf.minim.*;
import ddf.minim.analysis.*;
AudioInput in;
FFT fft;
void setup()
{
size(1024, 600);
noSmooth();
Minim.start(this);
in = Minim.getLineIn();
fft = new FFT(in.bufferSize(), in.sampleRate());
}
void draw()
{
background(0);
fft.forward(in.mix);
stroke(255);
for(int i = 0; i < fft.specSize(); i++)
line(i*2+1, height, i*2+1, height - fft.getBand(i)*10);
}
void stop()
{
in.close();
Minim.stop();
super.stop();
}
答案 8 :(得分:1)
将实际的模拟音频转换为数字形式涉及两个步骤。
<强>取样强>
对连续波形(在这种情况下,音频)进行采样的速率称为采样率。人类感知的频率范围是20 - 20,000 Hz。但是,CD使用奈奎斯特采样定理,这意味着采样率为44,100 Hz,覆盖的频率范围为0 - 22,050Hz。
量化
从“采样”中收到的离散值集合。现在需要将阶段转换为有限数量的值。 8位量化提供256个可能值,而16位量化提供高达65,536个值。
答案 9 :(得分:0)
答案都与采样频率有关,但不解决问题。我想,一个特定的声音快照包括许多不同频率的单个振幅(比如你在键盘上同时击中A和C,A更响亮)。如何以16位数记录?如果您所做的只是测量振幅(声音的大小),您如何获得不同的音符?
啊!我想我从这个评论中得到了它:&#34;然后将这个数字转换成扬声器振膜的线性位移。&#34;这些音符表示振膜的振动速度。这就是为什么你需要每秒44,000个不同的值。音符大约为1000赫兹,因此纯音符会使光圈每秒进出约1000次。整个管弦乐队的录音在整个地方都有许多不同的音符,并且奇迹般地可以转换成振膜运动的单一时间历史。隔膜被指示每秒44,000次移入或移出一点点,这个简单(长)的数字列表可以代表Beyonce到Beethoven!