进行音乐文件操作时,总是面临一个问题:需要适配很多种音频文件格式 。如: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.h、sndfile.lib、sndfile.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设置。cppfloat norm = 0; sf_command(sf, SFC_SET_NORM_FLOAT, &norm, sizeof(norm)); -
文件资源管理 :C API 需要手动调用
sf_close()关闭文件,而 C++ 封装SndfileHandle会自动管理资源,无需手动关闭。
5. 总结
本文介绍了 libsndfile 的基本使用方法,包括打开文件、读取音频数据、关闭文件等。同时,我们也提到了一些注意事项。希望本文对你有所帮助!