wav文件格式简介
wav文件由一个RIFF块(Resource Interchange File Format,资源互换文件格式)组成,其中包含一个"fmt "块和一个"data"块。
RIFF块包含了文件的总体信息,具体如下
字段
|
大小 (字节)
|
含义
|
ChunkID
|
4
|
固定的4个字符:“RIFF”
|
ChunkSize
|
4
|
wav文件的总大小-8字节,(不包含ChunkID 和ChunkSize本身的大小)
|
Format
|
4
|
固定的4个字符:“WAVE”
|
"fmt "块包含了音频数据的格式信息,具体如下:
字段
|
大小 (字节)
|
含义
|
Subchunk1ID
|
4
|
固定的4个字符 "fmt ",注意最后一个字符为空格且不可省略
|
Subchunk1Size
|
4
|
fmt 块的大小
|
AudioFormat
|
2
|
编码格式
|
NumChannels
|
2
|
声道数,1:单声道,2:双声道
|
SampleRate
|
4
|
采样率
|
ByteRate
|
4
|
码率,每秒传输的字节数,计算方法:SampleRate * NumChannels * BitsPerSample / 8
|
BlockAlign
|
2
|
块对其,播放时一次性需要处理的字节,计算方法:BitPerSample * NumChannels / 8
|
BitsPerSample
|
2
|
采样位数,一般为8,16,32,64等
|
"data"块包含了实际的音频数据,具体如下
字段
|
大小 (字节)
|
含义
|
Subchunk2ID
|
4
|
固定4个字符:''data"
|
Subchunk2Size
|
4
|
pcm原始音频数据的大小,单位:字节
|
wav文件头定义
//文件名:WAVEHeader.h
//"RIFF"块
struct RIFF_CHUNK {
char chunkID[4];//"RIFF"
int chunkSize;//整个文件的大小-8字节
char format[4];//"WAVE"
};
//"fmt "块
struct FMT_CHUNK {
char chunkID[4];//"fmt ",注意最后一个字节的内容为空格" ",不可省略
int chunkSize;
short audioFormat;
short numChannels;//单声道:1,立体声:2
int sampleRate;//采样率
int byteRate;//码率, SampleRate * NumChannels * BitsPerSample/8
short blickAlign;//NumChannels * BitsPerSample
short bitsPerSample;//8bits=8,16bit=16,以此类推
};
//"data" 块
struct DATA_CHUNK
{
char chunkID[4];//"data"
int chunkSize;//pcm音频数据大小
};
struct WAVHeader
{
RIFF_CHUNK riff;
FMT_CHUNK fmt;
DATA_CHUNK data;
};
读取wav文件
ifstream fin("test_s8le.wav", ios::binary);
if (!fin) {
cout << "open file failed!" << endl;
return 1;
}
WAVHeader header;
//读取wav文件头并保存到header对象中
fin.read((char*) & header, sizeof(header));
if (strncmp(header.riff.chunkID, "RIFF", 4) != 0 || strncmp(header.riff.format, "WAVE", 4) != 0
|| strncmp(header.fmt.chunkID, "fmt ", 4) != 0 || strncmp(header.data.chunkID, "data", 4) != 0) {
cout << "file is not a valid WAV file" << endl;
return 1;
}
cout << "audio format:" << header.fmt.audioFormat << endl;
cout << "channel couts:" << header.fmt.numChannels << endl;
cout << "sample rate:" << header.fmt.sampleRate << endl;
cout << "byte rate:" << header.fmt.byteRate << endl;
cout << "bits per sample:" << header.fmt.bitsPerSample << endl;
cout << "data size:" << header.data.chunkSize << endl;
cout <<" ---------" << endl;
char* pcmData = new char[header.data.chunkSize];
//读取wav文件的pcm数据部分,保存到char 数组中
fin.read(pcmData, header.data.chunkSize);
读取背景音文件
//读取背景音文件
ifstream finBg("background_s8le.wav", ios::binary);
if (!finBg) {
return 1;
}
WAVHeader bgHeader = {};
finBg.read((char*)&bgHeader, sizeof(header));
cout << "audio format:" << bgHeader.fmt.audioFormat << endl;
cout << "channel couts:" << bgHeader.fmt.numChannels << endl;
cout << "sample rate:" << bgHeader.fmt.sampleRate << endl;
cout << "byte rate:" << bgHeader.fmt.byteRate << endl;
cout << "bits per sample:" << bgHeader.fmt.bitsPerSample << endl;
cout << "data size:" << bgHeader.data.chunkSize << endl;
char* bgPcmData = new char[bgHeader.data.chunkSize];
finBg.read(bgPcmData, bgHeader.data.chunkSize);
音频混音
//----混音start--------------------------
//音频格式:8bit 8000hz 1channels
//使用算法:线性叠加后求平均
//优点:不会产生溢出,噪音较小;
//缺点:衰减过大,影响输出音频质量;
int maxSize = header.data.chunkSize > bgHeader.data.chunkSize ? header.data.chunkSize : bgHeader.data.chunkSize;
char* targetPcmData = new char[maxSize];
for (int i = 0; i < maxSize; i++) {
if (i < header.data.chunkSize && i < bgHeader.data.chunkSize) {
targetPcmData[i] = (char)(((int16_t)pcmData[i] + (int16_t)bgPcmData[i]) / 2);
}
else if (i < header.data.chunkSize) {
targetPcmData[i] = pcmData[i];
}
else {
targetPcmData[i] = bgPcmData[i];
}
}
//--混音end------------------------------------------------------
使用Windows WaveOut 相关API播放混音后的音频数据
WAVEFORMATEX waveFormat;
/*
WAVEFORMATEX是一种数据结构,用于指定波形音频流的数据格式。它包含以下字段:
wFormatTag:设置波形声音的格式。
nChannels:设置音频文件的通道数量,对于单声道的声音,此值为1;对于立体声,此值为2。
nSamplesPerSec:设置每个声道播放和记录时的样本频率。
nAvgBytesPerSec:设置每秒平均字节数。
nBlockAlign:设置数据块的对齐方式,即最小数据的原子大小。
wBitsPerSample:设置每个样本的位数。
cbSize:设置此结构的大小。
*/
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nChannels = header.fmt.numChannels;
waveFormat.nSamplesPerSec = header.fmt.sampleRate;
waveFormat.nBlockAlign = header.fmt.blickAlign;
waveFormat.wBitsPerSample = header.fmt.bitsPerSample;
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec*waveFormat.wBitsPerSample/8;
waveFormat.cbSize = 0;
waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, (DWORD_PTR)0, 0, CALLBACK_NULL);
waveOutHdr.lpData = targetPcmData;
waveOutHdr.dwBufferLength = maxSize;
waveOutPrepareHeader(hWaveOut, &waveOutHdr, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, &waveOutHdr, sizeof(WAVEHDR));
将混音后的数据保存到新的wav文件中
//输出混音后的数据到wav文件
HWAVEOUT hWaveOut; // waveOut设备句柄
WAVEHDR waveOutHdr; // waveOut数据块头
ofstream fout("output.wav", ios::binary);
if (!fout) {
cout << "output.wav create failed" << endl;
return 1;
}
WAVHeader oheader = header.data.chunkSize > bgHeader.data.chunkSize ? header : bgHeader;
fout.write((char*)&oheader, sizeof(oheader));
fout.write(targetPcmData, maxSize);
源码下载