一、预备知识:
PCM(Pulse Code Modulation,脉冲编码调制)是一种将模拟信号转换为数字信号的编码方式。在音频处理中,PCM是最常用的格式之一,用来表示原始的未压缩数字音频数据。PCM将连续的模拟音频波形信号采样并量化成一系列的数字值,通常是通过以下几个参数定义的:
1、采样率(Sample Rate):每秒采样的次数,通常用赫兹(Hz)表示,比如常见的44100 Hz(CD质量)或48000 Hz(视频音频标准)。
2、量化位深(Bit Depth):每个采样的位数,比如8位、16位、24位等。位深越高,音频的动态范围和精度越高。常见的位深是16位。
3、声道数(Channels):例如单声道(Mono)或立体声(Stereo),单声道数据有一个音频通道,而立体声则有左右两个通道。
二、PCM音频数据的存储形式
在PCM格式中,每个采样点直接保存了音频波形的数值(对应特定时间点的音量)。对于16位的PCM音频数据,每个采样点用2个字节存储,常见的存储方式是Little Endian(低位字节在前)。
PCM数据本身不带有文件头或元数据信息,只是纯音频样本数据。因此在保存到文件时通常会包装成WAV等带文件头的格式,以便保存采样率、位深和通道数等信息,从而使播放器可以正确解码音频数据。
如果采集到的是 double 类型的电压数据数组,则需要先将这些数据转换为合适的整数格式(例如 int16_t 或 int32_t)以符合 WAV 文件的要求。WAV 文件的 PCM 数据通常使用整数表示(例如 16 位或 24 位),而不是浮点数。因此,需要将 double 类型的电压值进行缩放和量化,以适配整数范围。
以下是步骤和实现代码:
2.1、将 double 型电压转换为 PCM 整数数据
假设电压数据的值范围在 -1.0 到 1.0 之间,可以将它们缩放到 16 位 PCM 的范围(-32768 到 32767)。如果电压值范围不同,需要先将其归一化或缩放到 [-1.0, 1.0] 之间。
如果使用新超仁达的NET-2406T采集到的电压数据范围在 -5.0V 到 5.0V 之间,那么在将这些电压值转换为 PCM 格式之前,需要先对其进行缩放,使其适配 24 位 PCM 数据的范围(-8388608 到 8388607)。下面是处理这个过程的步骤(详细请联系新超仁达科技,网址:www.xckz.com)。
处理步骤:
1)、缩放电压数据
将电压数据从 -5.0 到 5.0 的范围转换到 -1.0 到 1.0 的范围。通过归一化来实现。这样,-5.0 变为 -1.0,5.0 变为 1.0。
2)、转换为 24 位 PCM 数据
将缩放后的值乘以 8388607(即 2^23 −1),得到合适的整数值范围。
2.2、编写代码转换并保存为 WAV 文件(部分代码,详细联系新超仁达科技网址:www.xckz.com)
#include
#include
#include
#include
// WAV文件头结构
struct WAVHeader {
char chunkID[4]; // "RIFF"
uint32_t chunkSize; // 文件大小 - 8字节
char format[4]; // "WAVE"
char subchunk1ID[4]; // "fmt "
uint32_t subchunk1Size; // PCM = 16
uint16_t audioFormat; // PCM = 1
uint16_t numChannels; // 声道数
uint32_t sampleRate; // 采样率
uint32_t byteRate; // byteRate = SampleRate * NumChannels * BitsPerSample / 8
uint16_t blockAlign; // blockAlign = NumChannels * BitsPerSample / 8
uint16_t bitsPerSample; // 位深
char subchunk2ID[4]; // "data"
uint32_t subchunk2Size; // data size = NumSamples * NumChannels * BitsPerSample / 8
};
void writeWAV(const std::string& filename, const std::vector& audioData,
int sampleRate, int numChannels, int bitsPerSample) {
// 创建WAV文件头
// 省略核心代码
//…
// 省略核心代码
}
}
三、立体声
对于音频应用,如果需要更高的音频质量,使用立体声(2 个通道)或更多通道可能会带来更丰富的音频体验。在使用多个传感器进行数据采集,需要确保所有传感器的信号能够有效同步。这对于正确处理多通道音频信号非常重要。
在 WAV 文件中,如果使用两个通道(例如立体声),PCM 数据的存储格式会有所不同。每个音频样本将包含两个通道的数据,通常以交替的方式存储,即左声道和右声道的样本交替出现。
立体声PCM 数据的存储格式
假设每个样本是 16 位 PCM(即每个样本使用 2 字节),那么对于两个通道的 PCM 数据,存储格式将如下所示:
样本 1:左声道(L1) 右声道(R1)
样本 2:左声道(L2) 右声道(R2)
样本 3:左声道(L3) 右声道(R3)
// 假设你有两个通道的 PCM 数据
std::vector leftChannel = {1000, 2000}; // 左声道数据
std::vector rightChannel = {-1000, -2000}; // 右声道数据
std::vector pcmData;
// 将 PCM 数据交替存放到 pcmData 中
for (size_t i = 0; i < leftChannel.size(); ++i) {
// 将左声道数据添加到 pcmData
pcmData.push_back(static_cast((leftChannel[i] >> 0) & 0xFF)); // 低位
pcmData.push_back(static_cast((leftChannel[i] >> 8) & 0xFF)); // 高位
// 将右声道数据添加到 pcmData
pcmData.push_back(static_cast((rightChannel[i] >> 0) & 0xFF)); // 低位
pcmData.push_back(static_cast((rightChannel[i] >> 8) & 0xFF)); // 高位
}
重要的注意事项
1、交替存储:确保左声道和右声道的样本交替存储,这是处理立体声音频的标准格式。
2、字节顺序:根据你的平台,确保字节顺序(大端或小端)正确。例如,通常在 x86 系统中采用小端字节序。
3、位深:上述示例假设为 16 位样本。如果你的数据是 24 位或其他位深,需要相应地调整数据存储方式(例如,24 位样本需要 3 字节)。
4、采样率和其他头信息:在写入文件之前,确保在 WAV 文件头中正确设置采样率、位深和通道数。
总结
对于两个通道的 PCM 数据,将左声道和右声道的样本交替存储是处理立体声音频的标准方法。在将数据写入 WAV 文件时,确保数据格式正确,以保证音频的播放和处理效果。