最近在研究如何根据频率来产生wav音频文件。经过一番查阅资料发现了挺不错的解决方案,整理了一下写出来与大家分享。(ps:第一次写博客还不是很熟悉,诸如排版之类的问题还请大家见谅)
一.WAV文件格式
WAVE文件是非常简单的一种RIFF文件,它的格式类型为”WAVE”。RIFF块包含两个子块,这两个子块的ID分别是”fmt”和”data”,其中”fmt”子块由结构PCMWAVEFORMAT所组成,其子块的大小就是sizeofof(PCMWAVEFORMAT),数据组成就是PCMWAVEFORMAT结构中的数据。(详细介绍于http://blog.csdn.net/zhihu008/article/details/7854529中查看,这里只作简要介绍)
wav文件结构
PCMWAVEFORMAT结构定义如下:
typedef struct
{
WAVEFORMAT wf; // 波形格式;
WORD wBitsPerSample;//WAVE文件的采样大小;
} PCMWAVEFORMAT;
WAVEFORMAT结构定义如下:
typedef struct
{
WORD wFormatag;//编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等
WORD nChannls;//声道数,单声道为1,双声道为2
DWORD nSamplesPerSec;//采样频率
DWORD nAvgBytesperSec;//每秒的数据量
WORD nBlockAlign;//块对齐(每个采样的字节长度)
} WAVEFORMAT;
WAV文件头结构:
typedef struct
{
char chRIFF[4];//"RIFF"
DWORD dwRIFFLen;//总长度-8
char chWAVE[4];//"WAVE"
char chFMT[4];//"fmt "
DWORD dwFMTLen;//sizeof(PCMWAVEFORMAT )
PCMWAVEFORMAT pwf;
char chDATA[4];//"data"
DWORD dwDATALen;//音频数据长度
}WaveHeader;
其中的字符数组都是固定格式。以下分别对应8、16比特单双通道的数据存储格式图:
8bit、单通道:
8bit、双通道:
16bit、单通道:
16bit、双通道:
二.实现代码
代码有个很玄学的地方,我也不懂该怎么描述。的确能创建出指定频率的wav文件,但是生成8bit和16bit波形数据有一些不懂,我找了很久也没能发现原因。其中值得注意的是,当创建并读写wav文件时,要以二进制打开。
#include <windows.h>
#include <mmsystem.h>
#include <iostream>
#include<fstream>
#include<math.h>
#define WAVE_HEAD_LENGTH 44//wav头文件长度
#define m_samplefreq 44100
#define m_channels 2
#define m_channelbits 8
#define MATH_PI 3.1415
using namespace std;
//.wav文件的文件头结构
typedef struct
{
char chRIFF[4];
DWORD dwRIFFLen;
char chWAVE[4];
char chFMT[4];
DWORD dwFMTLen;
PCMWAVEFORMAT pwf;
char chDATA[4];
DWORD dwDATALen;
//UINT8* pBufer;
}WaveHeader;
void MakeWaveData(int rate, int freq, int amp, char* p, int len)//采样率、频率、音量、采样点数
{
int flag = 0;
if (m_channelbits == 16) //16位
{
if (m_channels == 1)
{
for (int i = 0; i < len; i++)
{
INT16 v = amp/100*32768 * sin(2 * MATH_PI * freq * i / rate);
*(p + flag) = v & 0xFF;//低8位
*(p + flag + 1) = (v >> 8) & 0xFF;//16bit量化 高8位
flag += 2;
}
}
else
{
for (int i = 0; i < len; i++)
{
INT16 vl = amp / 100 * 32768 * sin(2 * MATH_PI * freq * i / rate) ;
INT16 vr = amp / 100 * 32768 * sin((2 * MATH_PI * freq * (i+5) )/ rate) ;
*(p + flag) = (vl & 0xFF);
*(p + flag + 1) = ((vl >> 8) & 0xFF);
*(p + flag + 2) = (vr & 0xFF);
*(p + flag + 3) = ((vr >> 8) & 0xFF);
flag += 4;
}
}
}
else
{
if (m_channels == 1)
{
for (int i = 0; i < len; i++)
{
*(p + i) = sin(i * (MATH_PI * 2) / rate * freq) * amp * 128 / 100 + 128;
}
}
else
{
for (int i = 0; i < len; i++)
{
*(p + flag)= sin(i * (MATH_PI * 2) / rate * freq) * amp * 128 / 100+128;
*(p + flag + 1)= sin((i+5) * (MATH_PI * 2) / rate * freq) * amp * 128 / 100+128;
flag += 2;
}
}
}
}
int Create(int freq, int volume, int durations)//频率、音量、持续时间
{
WaveHeader *pHeader = new WaveHeader;
DWORD totalLen = (m_samplefreq * m_channels * m_channelbits / 8) * durations + 44;//文件总长度=(采样率 * 通道数 * 比特数 / 8) * 持续时间(s)
pHeader->chRIFF[0] = 'R';
pHeader->chRIFF[1] = 'I';
pHeader->chRIFF[2] = 'F';
pHeader->chRIFF[3] = 'F';
pHeader->dwRIFFLen = totalLen - 8;//文件的总长度-8bits
pHeader->chWAVE[0] = 'W';
pHeader->chWAVE[1] = 'A';
pHeader->chWAVE[2] = 'V';
pHeader->chWAVE[3] = 'E';
pHeader->chFMT[0] = 'f';
pHeader->chFMT[1] = 'm';
pHeader->chFMT[2] = 't';
pHeader->chFMT[3] = ' ';
pHeader->dwFMTLen = 0x0010;//一般情况下Size为16,如果为18则最后多了2个字节的附加信息
pHeader->pwf.wf.wFormatTag = 0x0001;//编码方式
pHeader->pwf.wf.nChannels = m_channels; //1为单通道,2为双通道
pHeader->pwf.wf.nSamplesPerSec = m_samplefreq; //=44.1KHz
pHeader->pwf.wf.nAvgBytesPerSec = m_samplefreq * m_channels * m_channelbits / 8;//每秒所需字节数
pHeader->pwf.wf.nBlockAlign = m_channels * m_channelbits / 8;//一个采样的字节数
pHeader->pwf.wBitsPerSample = m_channelbits;//16位,即设置PCM的方式为16位立体声(双通道)
pHeader->chDATA[0] = 'd';
pHeader->chDATA[1] = 'a';
pHeader->chDATA[2] = 't';
pHeader->chDATA[3] = 'a';
pHeader->dwDATALen = totalLen - WAVE_HEAD_LENGTH;//数据的长度,=文件总长度-头长度(44bit)
char *pWaveBuffer = new char[totalLen]; //音频数据
memcpy(pWaveBuffer, pHeader, WAVE_HEAD_LENGTH);
MakeWaveData(pHeader->pwf.wf.nSamplesPerSec, freq, volume, pWaveBuffer+ WAVE_HEAD_LENGTH, m_samplefreq*durations);//采样点数
ofstream ocout;
ocout.open("D:\\newWave.wav", ios::out | ios::binary);//以二进制形式打开文件
if (ocout)
ocout.write(pWaveBuffer, totalLen);
else
return 0;
ocout.close();
delete(pHeader);
return 1;
}
int main()
{
if (Create(10000, 100, 5))
cout << "创建成功!" << endl;
else
cout << "创建失败!" << endl;
return 0;
}
生成的8bit音频文件频谱:
参考资料:
http://blog.csdn.net/zhihu008/article/details/7854529#comments
http://bbs.csdn.net/topics/390026541/