- 采样率:一秒内对信号的采样次数,单位HZ,采样次数越高,音频质量越好,相同时间的PCM数据量也就越大,一般音频的采样率为16000HZ,44100HZ,48000HZ。
- 音频数据量化时有个基准参考线0,因此一遍音频数据都是有符号的
- 采样位宽:一个采样点用多大的数据来表示,有一个字节(8位),和两个字节(16位)之分,是用来衡量声音的波动变化的一个参数。
- 字节序:音频一般是小端存储
- 声道数:有单声道,双声道等
PCM数据排列格式
下图是一个16位,小端存储的pcm,从图中可以看出,
- 两个声道的pcm数值并不一定是相同的
- 小端存储,其实际的值是0xE64A,0xF72C,从文件读取时需要注意处理。下面给出了处理函数
下面是从一个位宽为16的PCM文件中读取PCM数据的代码
/**
buffer:用于存放读取采样点的缓存
maxSamples:一个通道最多读取多少个采样点
channels:通道数
return:返回实际读取的采样点数
*/
#define READ_BUFFER_SIZE 4096
int readSamplesFromPCMFile(FILE *pcmFile, short *buffer, int maxSamples, int channels) {
unsigned char buf[READ_BUFFER_SIZE];
if (maxSamples * channels * 2 > READ_BUFFER_SIZE) {
maxSamples = READ_BUFFER_SIZE / (channels << 1);
}
int read_bytes = fread(buf, sizeof(char), maxSamples * channels * 2, pcmFile);
int samplesRead = read_bytes / (channels << 1);
int bytePos = 0;
for (int i = 0; i < samplesRead * channels; i++) {
short sample = buf[bytePos++];
sample |= (unsigned int)buf[bytePos++] << 8;
*buffer++ = sample;
}
return samplesRead;
}
下面是使用sonic库读取PCM做倍速处理后,保存WAV的代码,地址:https://gitcode.net/xiwenhec/soundspeed
#include "sonic.h"
#include "wave.h"
#include <corecrt_wstdio.h>
#include <cstdio>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 2048
#define READ_BUFFER_SIZE 4096
struct PCMFile {
std::string name;
int sampleRate;
int numChannels;
};
/**
buffer:用于存放读取采样点的缓存
maxSamples:一个通道最多读取多少个采样点
channels:通道数
return:返回实际读取的采样点
*/
int readSamplesFromPCMFile(FILE *pcmFile, short *buffer, int maxSamples, int channels) {
unsigned char buf[READ_BUFFER_SIZE];
if (maxSamples * channels * 2 > READ_BUFFER_SIZE) {
maxSamples = READ_BUFFER_SIZE / (channels << 1);
}
int read_bytes = fread(buf, sizeof(char), maxSamples * channels * 2, pcmFile);
int samplesRead = read_bytes / (channels << 1);
int bytePos = 0;
for (int i = 0; i < samplesRead * channels; i++) {
short sample = buf[bytePos++];
sample |= (unsigned int)buf[bytePos++] << 8;
*buffer++ = sample;
}
return samplesRead;
}
/**
test.pcm:48000,2声道,16位
*/
void runSonicPCM(const PCMFile &inputPCM, const char *outFileName, float speed, float pitch, float rate, float volume,
int outputSampleRate, int emulateChordPitch, int quality) {
FILE *inFile = nullptr;
// 打开输入文件
errno_t error = fopen_s(&inFile, inputPCM.name, "rb+");
if (error != 0) {
fprintf(stderr, "Unable to open pcn file %s\n", inputPCM.name);
return;
}
printf("open input file:%s success\n", inputPCM.name);
int sampleRate = outputSampleRate == 0 ? inputPCM.sampleRate : outputSampleRate;
// 打开输出文件
waveFile outFile = openOutputWaveFile(outFileName, sampleRate, inputPCM.numChannels);
if (error != 0) {
fclose(inFile);
fprintf(stderr, "Unable to open wave file %s for writing\n", outFileName);
exit(1);
}
printf("open output file:%s success.\n", outFileName);
sonicStream stream = sonicCreateStream(sampleRate, inputPCM.numChannels);
sonicSetSpeed(stream, speed);
sonicSetPitch(stream, pitch);
sonicSetRate(stream, rate);
sonicSetVolume(stream, volume);
sonicSetChordPitch(stream, emulateChordPitch);
sonicSetQuality(stream, quality);
int samplesRead = 0, samplesWritten = 0;
short inBuffer[BUFFER_SIZE], outBuffer[BUFFER_SIZE];
do {
samplesRead = readSamplesFromPCMFile(inFile, inBuffer, BUFFER_SIZE / inputPCM.numChannels, inputPCM.numChannels);
if (samplesRead == 0) {
sonicFlushStream(stream);
} else {
sonicWriteShortToStream(stream, inBuffer, samplesRead);
}
do {
samplesWritten = sonicReadShortFromStream(stream, outBuffer, BUFFER_SIZE / inputPCM.numChannels);
if (samplesWritten > 0) {
printf("samplesWrite:%d\n", samplesWritten);
writeToWaveFile(outFile, outBuffer, samplesWritten);
}
} while (samplesWritten > 0);
} while (samplesRead > 0);
sonicDestroyStream(stream);
fclose(inFile);
closeWaveFile(outFile);
}
int main() {
PCMFile pcmInputFile{
.name = "res/test.pcm",
.sampleRate = 48000,
.numChannels = 2
};
const char *outputFileName = "res/test_2x.wav";
runSonicPCM(pcmInputFile, outputFileName, 1.5f, 1.0, 1.0, 1.0, 0, 0, 0);
return 0;
}