音频如何用数字表示?

时间:2009-04-09 03:46:30

标签: audio

我喜欢思考一切如何,并用数字表示。例如,明文由类似ASCII的代码表示,图像由RGB值表示。这些是表示文本和图像的最简单方法。

用数字表示音频的最简单方法是什么?我想学习如何编写与音频一起工作的程序,并认为这是一个很好的开始方式。不过,我似乎无法在互联网上找到任何好的解释。

10 个答案:

答案 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;
}

GitHub upstream

生成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;
}

GitHub upstream

对于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

解释如下:https://superuser.com/questions/1041816/combine-one-image-one-audio-file-to-make-one-video-using-ffmpeg/1041818#1041818

这是一个更加面向物理的音频生成视图: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)

我认为开始播放音频的好方法是使用ProcessingMinim。 该程序将从麦克风中绘制声音的频谱!

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)

将实际的模拟音频转换为数字形式涉及两个步骤。

  1. 取样
  2. 量化
  3. <强>取样

    对连续波形(在这种情况下,音频)进行采样的速率称为采样率。人类感知的频率范围是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!