最近在开发一个运行于 Debian 的 C++ 音频程序,需要通过声卡向指定音频通道输出 PCM 音频数据。为了测试整个音频输出链路,主管要求准备以下几种测试音:
- 50Hz
- 100Hz
- 200Hz
- 400Hz
- 1000Hz
- 2000Hz
同时要求音频格式统一为:
- 单声道(Mono)
- PCM编码
- 16位(16-bit)
- 8000Hz采样率
刚开始看到这个需求时,我一度以为这里的 50Hz、100Hz 指的是采样率,后来才发现它表示的是声音本身的频率(音调)。
一、采样率和音频频率的区别
这是数字音频中两个最容易混淆的概念。
采样率(Sample Rate)
采样率表示:
每秒钟采集(或播放)多少个采样点。
例如:
8000Hz
表示:
每秒共有8000个采样点
采样率决定的是数字音频的时间分辨率。
音频频率(Frequency)
音频频率表示:
声音每秒振动多少次。
例如:
50Hz
表示:
声音每秒振动50次
因此:
- 8000Hz采样率
- 50Hz测试音
并不是同一个概念。
举个例子:
生成一段1秒钟的50Hz正弦波:
采样率 = 8000Hz
声音频率 = 50Hz
意味着:
8000 ÷ 50 = 160
也就是说:
160个采样点组成一个完整波形。
如果换成1000Hz:
8000 ÷ 1000 = 8
仅需要8个采样点便组成一个完整周期,因此音调会高很多。
二、为什么8000Hz采样率还能播放2000Hz?
这是奈奎斯特采样定理决定的。
采样率为:
Fs = 8000Hz
理论上能够表示的最高频率为:
Fs / 2 = 4000Hz
因此:
| 测试频率 | 是否可表示 |
|---|---|
| 50Hz | √ |
| 100Hz | √ |
| 200Hz | √ |
| 400Hz | √ |
| 1000Hz | √ |
| 2000Hz | √ |
所以主管要求的几个测试频率都完全没有问题。
三、为什么要准备这些频率?
这些都是音频工程中最常见的单频测试音(Pure Tone)。
不同频率可以测试不同的音频响应。
| 频率 | 主要用途 |
|---|---|
| 50Hz | 超低频测试 |
| 100Hz | 低频测试 |
| 200Hz | 低中频测试 |
| 400Hz | 中频测试 |
| 1000Hz | 标准测试频率 |
| 2000Hz | 高频测试 |
例如:
如果:
- 50Hz没有输出
- 100Hz很弱
- 1000Hz正常
通常说明设备低频响应存在问题。
四、PCM里面保存的是什么?
PCM文件里保存的是一系列采样值。
对于正弦波而言,每一个采样值都可以通过下面公式计算:
sample = amplitude * sin(2πft)
其中:
- amplitude:振幅
- f:测试频率
- t:当前采样时间
例如生成50Hz:
sample = 26214 * sin(2 * M_PI * 50 * n / 8000.0);
把50修改为:
100
200
400
1000
2000
即可生成对应频率的测试音。
五、为什么50Hz几乎听不到?
生成测试文件以后,我首先用自己的笔记本试听。
结果发现:
50Hz几乎没有声音。
后来确认,这是完全正常的现象。
原因并不是文件有问题,而是:
笔记本扬声器根本放不出来这么低的频率。
绝大多数笔记本扬声器:
- 150Hz以下开始明显衰减
- 100Hz以下输出已经很弱
- 50Hz基本无法正常播放
这是由扬声器尺寸决定的。
不同频率的大致听感
| 频率 | 听感 |
|---|---|
| 50Hz | 几乎听不到,仅有低频震动感 |
| 100Hz | 较闷、较轻 |
| 200Hz | 低沉 |
| 400Hz | 中频嗡鸣 |
| 1000Hz | 标准测试音,非常明显 |
| 2000Hz | 比较尖锐,长时间试听容易疲劳 |
因此:
1000Hz、2000Hz听起来比较刺耳,是正常现象。
测试音本来就不是为了"好听",而是为了测试设备。
六、为什么音频测试喜欢用正弦波?
正弦波最大的特点就是:
只包含一个频率。
例如:
1000Hz
整个信号中只有1000Hz。
因此很容易判断:
- 是否存在失真
- 是否有杂音
- 左右(多)声道是否正常
- 输出音量是否一致
相比之下,音乐包含大量不同频率,很难定位问题。
因此:
音频行业常见测试信号包括:
- 正弦波(Sine)
- 白噪声(White Noise)
- 粉红噪声(Pink Noise)
- Sweep(扫频)
七、C++生成16bit/8000Hz测试音
生成测试音其实非常简单。
核心代码如下:
cpp
#include <cmath>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
const int SAMPLE_RATE = 8000;
const int BITS_PER_SAMPLE = 16;
const int CHANNELS = 1;
const int DURATION = 5; // 秒
const double AMPLITUDE = 0.8; // 最大幅值80%
#pragma pack(push, 1)
struct WavHeader
{
char riff[4] = {'R','I','F','F'};
uint32_t fileSize;
char wave[4] = {'W','A','V','E'};
char fmt[4] = {'f','m','t',' '};
uint32_t fmtSize = 16;
uint16_t audioFormat = 1;
uint16_t numChannels = CHANNELS;
uint32_t sampleRate = SAMPLE_RATE;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample = BITS_PER_SAMPLE;
char data[4] = {'d','a','t','a'};
uint32_t dataSize;
};
#pragma pack(pop)
void GenerateToneWav(double frequency)
{
std::string fileName =
"tone_" + std::to_string((int)frequency) + "Hz.wav";
std::ofstream file(fileName, std::ios::binary);
if (!file)
{
std::cout << "创建失败:" << fileName << std::endl;
return;
}
int totalSamples = SAMPLE_RATE * DURATION;
int dataSize = totalSamples * CHANNELS * sizeof(int16_t);
WavHeader header;
header.dataSize = dataSize;
header.byteRate = SAMPLE_RATE * CHANNELS * sizeof(int16_t);
header.blockAlign = CHANNELS * sizeof(int16_t);
header.fileSize = 36 + dataSize;
file.write(reinterpret_cast<char*>(&header), sizeof(header));
const double maxValue = 32767.0 * AMPLITUDE;
for (int i = 0; i < totalSamples; ++i)
{
double sample =
maxValue *
sin(2.0 * M_PI * frequency * i / SAMPLE_RATE);
int16_t pcm = static_cast<int16_t>(sample);
file.write(reinterpret_cast<char*>(&pcm), sizeof(pcm));
}
file.close();
std::cout << "生成:" << fileName << std::endl;
}
int main()
{
std::vector<int> freqs =
{
50,
100,
200,
400,
1000,
2000
};
for (auto f : freqs)
{
GenerateToneWav(f);
}
std::cout << "\n全部生成完成!" << std::endl;
return 0;
}
然后按照WAV文件格式:
WAV Header
+
PCM数据
即可得到标准WAV文件。
最终生成:
tone_50Hz.wav
tone_100Hz.wav
tone_200Hz.wav
tone_400Hz.wav
tone_1000Hz.wav
tone_2000Hz.wav
参数统一为:
Mono
16bit
8000Hz
PCM
5秒
Windows、Linux以及各种音频播放器均可直接播放。
最直观的是通过Audacity软件来查看对比
可以看到50hz和400hz、1000hz一个正弦波的频率差异。Audacity下载地址:Audacity ® | Downloads
建议直接下载压缩包

八、总结
在音频开发过程中,采样率 和声音频率是两个完全不同的概念:
- 采样率决定数字音频每秒包含多少采样点;
- 声音频率决定音调高低。
主管要求准备的50Hz~2000Hz测试音,本质上就是一组不同频率的纯正弦波(Pure Tone),主要用于验证声卡、功放以及整个音频输出链路在不同频段的工作情况。
实际测试中,发现50Hz在笔记本扬声器上几乎听不到,这是由于扬声器低频响应能力有限造成的,并不是音频文件生成错误。对于工业广播、专业音响或带低音单元的设备,这类低频测试音依然具有重要的测试价值。
通过编写一个简单的C++程序,即可自动生成满足 16位、8000Hz、单声道 要求的标准WAV测试文件,为后续的音频开发和调试提供便利。