用 std::vector 存 pcm 数据最稳妥,可避免字节序误读、越界和内存泄漏;需按采样率、位深、声道数预分配空间,处理时注意溢出截断与平台帧定义差异。

用 std::vector<int16_t></int16_t> 存 PCM 数据最稳妥
PCM 原始数据通常是线性量化样本,16 位有符号整数(int16_t)最常见。直接用 std::vector<int16_t></int16_t> 而不是裸指针或 std::vector<char></char>,能避免字节序误读、越界和内存泄漏问题。
常见错误是把 PCM 当作字节流塞进 std::vector<uint8_t></uint8_t>,后续做音量归一化、滤波等处理时还得手动每 2 字节拼成一个样本,极易出错。
- 采样率为 44.1kHz、单声道、16bit 的 PCM,每秒就是 44100 个
int16_t元素 - 双声道需按 LRLR 交错排列,即
vec[0]是左声道第 1 样本,vec[1]是右声道第 1 样本 - 初始化可预留空间:
std::vector<int16_t> pcm_data; pcm_data.reserve(expected_sample_count);</int16_t>
从文件或设备读 PCM 时注意字节序和对齐
原始 PCM 文件(如 .raw)无头信息,必须明确知道:采样率、位深、声道数、字节序(通常是小端)、是否带 padding。否则 read() 进来的数据会整体偏移或反转。
例如用 fread 读二进制 PCM:
FILE* f = fopen("audio.raw", "rb");
std::vector<int16_t> samples;
samples.resize(44100 * 5); // 5 秒单声道
size_t n = fread(samples.data(), sizeof(int16_t), samples.size(), f);
fclose(f);</int16_t>
这里关键点:
-
fread返回的是成功读取的int16_t个数,不是字节数 —— 别用sizeof(char)当 size 参数 - Windows API 或 ALSA 录音回调返回的缓冲区,通常已按 host 字节序排好,无需再翻转
- 如果源数据是大端(如某些网络音频流),需在存入 vector 前用
ntohs()转换
对 std::vector<int16_t></int16_t> 做基础处理要避免溢出
音量放大、混音、DC 偏移修正等操作都涉及算术运算,int16_t 范围是 [-32768, 32767],直接加减极易溢出导致爆音。
- 放大 2 倍?先转
int32_t计算,再用std::clamp截断:static_cast<int16_t>(std::clamp(static_cast<int32_t>(s) * 2, -32768L, 32767L))</int32_t></int16_t> - 双声道混音(L+R)→ 单声道?结果要除以 2,否则超限;或改用右移:
(l + r) >> 1 - 求 RMS 能量?用
int64_t累加平方,最后开方,别用int16_t中间存平方值
传给音频 API(如 PortAudio、OpenSL ES)前确认内存布局
很多音频库要求缓冲区是连续、对齐、且格式严格匹配的。比如 PortAudio 的 PaStreamCallback 回调中,void *inputBuffer 指向的内存块,若你用 std::vector<int16_t>::data()</int16_t> 传入,必须确保:
- vector 未发生过重分配(即没调过
push_back等可能触发扩容的操作)—— 最好用reserve+resize预分配后只写不扩 - 目标 API 是否要求 16 字节对齐?
std::vector默认不保证,必要时用aligned_alloc+std::unique_ptr管理 - 若 API 要
int8_t*(如 Android OpenSL ES 的SL_DATAFORMAT_PCM),别强转 —— 应重新解释为字节流:reinterpret_cast<int8_t>(vec.data())</int8_t>,但长度要乘 2
实际中最容易被忽略的是:不同平台对“单帧”定义不一致 —— 有的按样本数(1 个 stereo frame = 2 个 int16_t),有的按字节数(= 4 字节),传错会导致音频拉伸或卡顿。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











