1. libsndfile

进行音乐文件操作时,总是面临一个问题:需要适配很多种音频文件格式 。如:WAV、AIFF、FLAC、OGG、MP3、AAC......每一种格式都有各自的结构、编码方式、压缩算法。通常都是借助FFmpeg进行处理,但是FFmpeg的API比较复杂,需要学习成本较高。

反观libsndfile则是一个简单、易用的音频读写库。它的具备如下特性:

  • 统一 API:同一套函数读写几十种音频格式,无需为每种格式编写解析代码
  • 跨平台:Windows / macOS / Linux 全平台支持
  • 双 API :C API 灵活底层,C++ 封装 SndfileHandle 提供 RAII 便利
  • 成熟稳定:1999 年诞生,25 年持续维护,被 Audacity、FFmpeg 等知名项目广泛使用
  • 多种数据类型:支持 short / int / float / double 四种精度的读写,适配不同应用场景

1. libsndfile

libsndfile 由 Erik de Castro Lopo 在 1999 年创建,它提供了C/C++ 两套 API。借助该库可以实现一套代码就可以读写几十种格式的音频文件。当前 libsndfile 支持超过 30 种文件格式和编码类型,包括但不限于:

大类 具体格式
未压缩格式 WAV, AIFF, AU, RAW
无损压缩 FLAC, ALAC (Apple Lossless)
有损压缩 Ogg/Vorbis, MPEG Audio, Opus
乐器/采样 XI (FastTracker), VOC
科学计算 MAT4/MAT5 (MATLAB), NIST (Sphere)
专业音频 CAF (Core Audio), RF64, W64

2. 使用 libsndfile

2.1 源码编译

2.1.1 下载源码

官方 GitHub 仓库:https://github.com/libsndfile/libsndfile

最新的稳定版本是 1.2.2,可以直接下载源码包或 clone 仓库。

2.1.2 编译

libsndfile 使用 CMake 构建系统,编译非常标准:

bash 复制代码
git clone https://github.com/libsndfile/libsndfile.git
cd libsndfile
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
2.1.3 编译选项

libsndfile 在 CMakeLists.txt 中提供了一系列编译选项,方便用户按需定制库的行为和产物:

选项 默认值 说明
BUILD_SHARED_LIBS OFF 构建共享库(Windows 下为 DLL);OFF 时构建静态库
BUILD_EXAMPLES ON 构建示例代码
BUILD_TESTING ON 构建测试程序(BUILD_SHARED_LIBS=ON 时自动禁用)
ENABLE_EXTERNAL_LIBS ON(如果找到依赖) 启用 Ogg、Vorbis、FLAC、Opus 支持
ENABLE_MPEG ON(如果找到依赖) 启用 MP3 支持
ENABLE_BOW_DOCS OFF 启用黑底白字文档主题
ENABLE_EXPERIMENTAL OFF 启用实验性代码,非专家勿用
ENABLE_STATIC_RUNTIME OFF Windows 平台启用静态运行时(MSVC/MinGW)

常用组合示例:

bash 复制代码
# 构建共享库(DLL),关闭工具和测试
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
    -DBUILD_SHARED_LIBS=ON \
    -DBUILD_EXAMPLES=OFF \
    -DBUILD_TESTING=OFF

# 构建带 Ogg/Vorbis/FLAC/Opus 支持的静态库
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
    -DBUILD_SHARED_LIBS=OFF \
    -DENABLE_EXTERNAL_LIBS=ON

2.2 使用 vcpkg 安装(推荐)

如果你使用 vcpkg 管理第三方库,一行命令搞定:

bash 复制代码
vcpkg install libsndfile

如果需要支持 Ogg/Vorbis/Opus 等编码:

bash 复制代码
vcpkg install libsndfile[external-libs]

vcpkg 会自动处理所有依赖,并且集成到 CMake 中,非常省心。

2.3 预编译包

对于 Windows 开发者,GitHub Releases 页面提供了预编译的 DLL 和库文件。见预编译包。直接将 sndfile.hsndfile.libsndfile.dll 放入你的项目即可。

3. 代码示例

下面我们通过一个完整的例子,演示如何使用 libsndfile 读取一个 WAV 文件。

3.1 完整代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <cstring>
#include <sndfile.h>
#include <windows.h>

int main()
{
    SetConsoleOutputCP(CP_UTF8);

    const char *filepath = "test.wav";

    // 1. 打开文件,获取音频信息
    SF_INFO sfinfo;
    std::memset(&sfinfo, 0, sizeof(sfinfo));

    SNDFILE *sf = sf_open(filepath, SFM_READ, &sfinfo);
    if (!sf)
    {
        std::cerr << "打开文件失败: " << sf_strerror(nullptr) << std::endl;
        return 1;
    }

    // 2. 打印文件信息
    std::cout << "  采样率: " << sfinfo.samplerate << " Hz\n";
    std::cout << "  通道数: " << sfinfo.channels << "\n";
    std::cout << "  总帧数: " << sfinfo.frames << "\n";
    std::cout << "  总时长: "
              << static_cast<double>(sfinfo.frames) / sfinfo.samplerate
              << " 秒\n";

    // 3. 使用 sf_readf_float 读取所有音频数据
    sf_count_t total_frames = sfinfo.frames;
    int channels = sfinfo.channels;
    std::vector<float> buffer(static_cast<size_t>(total_frames * channels));

    sf_count_t frames_read = sf_readf_float(sf, buffer.data(), total_frames);
    //sf_count_t frames_read = sf_read_float(sf, buffer.data(), total_frames*channels);
    std::cout << "读取帧数: " << frames_read << "\n";

    // 4. 计算各声道统计信息
    for (int ch = 0; ch < channels; ++ch)
    {
        double sum = 0, max_val = 0;
        for (sf_count_t i = 0; i < frames_read; ++i)
        {
            float val = buffer[i * channels + ch];
            sum += val;
            if (std::abs(val) > max_val)
                max_val = std::abs(val);
        }
        double mean = sum / frames_read;
        std::cout << "\n声道 " << ch + 1 << ":\n";
        std::cout << "  均值: " << mean << "\n";
        std::cout << "  最大值: " << max_val << "\n";
    }

    // 5. 关闭文件
    sf_close(sf);
    return 0;
}

3.2 函数解读

cpp 复制代码
//功能: 打开文件
//参数:
// filepath: 文件路径
// mode: 打开模式,SFM_READ、SFM_WRITE、SFM_RDWR
//    SFM_READ: 只读
//    SFM_WRITE: 只写
//    SFM_RDWR: 读写
// struct SF_INFO
// {	
//  sf_count_t	frames ;		/* Used to be called samples.  Changed to avoid confusion. */
// 	int			samplerate ;
// 	int			channels ;
// 	int			format ;
// 	int			sections ;
// 	int			seekable ;
// } ;
//返回值:SNDFILE 指针,失败时返回 nullptr
SNDFILE *sf_open(const char *filepath, int mode, SF_INFO *sfinfo);



//功能: 读取音频数据
//参数:
// sf: SNDFILE 指针
// buffer: 存储读取数据的缓冲区
// frames: 读取的帧数
//返回值:实际读取的帧数,失败时返回 0
sf_count_t sf_readf_float(SNDFILE *sf, float *buffer, sf_count_t frames);


//功能: 读取音频数据
//参数:
// sf: SNDFILE 指针
// buffer: 存储读取数据的缓冲区
// items: 读取的采样点数= frames * channels
//返回值:实际读取的采样点数
sf_count_t sf_read_float(SNDFILE *sf, float *buffer, sf_count_t items);


//功能: 关闭文件
//参数:
// sf: SNDFILE 指针
//返回值:成功返回 0,失败返回非 0
int sf_close(SNDFILE *sf);

3.3 C++ 接口

libsndfile 也提供了一个 C++ 封装 SndfileHandle,接口内部调用C API,但是他借助 RAII 机制和引用计数自动管理文件资源,日常使用比 C API 方便很多。

4. 注意事项

libsndfile 具备强大的音频格式支持能力,但是也有其限制。

  • 格式限制libsndfile 不处理实时音频流 (如麦克风输入、网络流媒体),它只专注于文件的读写。实时音频需要配合 PortAudio、RtAudio 等库使用。

  • MP3 支持有限libsndfile 对 MP3 的支持是只读的,且需要通过外部库(如 libmp3lame)支持。如果需要完整的 MP3 编码能力,建议直接使用 LAME 或 FFmpeg。

  • 浮点数据范围 :使用 sf_readf_float 读取时,数据会被归一化到 [-1.0, 1.0] 范围。如果要关闭这个归一化行为,可以使用 sf_command 设置。

    cpp 复制代码
    float norm = 0;
    sf_command(sf, SFC_SET_NORM_FLOAT, &norm, sizeof(norm));
  • 文件资源管理 :C API 需要手动调用 sf_close() 关闭文件,而 C++ 封装 SndfileHandle 会自动管理资源,无需手动关闭。

5. 总结

本文介绍了 libsndfile 的基本使用方法,包括打开文件、读取音频数据、关闭文件等。同时,我们也提到了一些注意事项。希望本文对你有所帮助!