C++音频开发:什么是50Hz、100Hz测试音?以及如何生成16bit/8000Hz的WAV测试文件

最近在开发一个运行于 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测试文件,为后续的音频开发和调试提供便利。

相关推荐
XH华2 小时前
C++语言第二章类和对象(下)
开发语言·c++
从零开始的代码生活_2 小时前
C++ stack、queue 与 priority_queue:容器适配器原理与实战
开发语言·c++·后端·学习·算法
techdashen2 小时前
Go 1.25 新增 `reflect.TypeAssert`:更直接、更高效地从 `reflect.Value` 取出类型值
开发语言·后端·golang
2zcode2 小时前
项目文档:基于MATLAB低采样率ISAR成像的快速稀疏重建算法研究
开发语言·算法·matlab
xixi09242 小时前
JMeter5.6.3基础使用教程1
java·开发语言
এ慕ོ冬℘゜2 小时前
原生 JS 实现自定义单选日历组件(无 UI 库、纯手写、禁用过去日期)
开发语言·javascript·ui
Ulyanov3 小时前
刚体动力学方程——牛顿-欧拉与陀螺效应的奥秘
开发语言·python·目标跟踪·雷达电子对抗·导引头
罗超驿3 小时前
15.Java异常处理:从束手无策到从容应对
java·开发语言
MoonBit月兔3 小时前
MoonBit v0.10.4版本更新
开发语言·人工智能·编程·moonbit
小樱花的樱花3 小时前
Linux 多线程编程:互斥锁(Mutex)详解
linux·c语言·开发语言