WAV文件结构
在Windows环境下,大部分多媒体文件都是按照资源互换文件格式(Resources lnterchange File Format)存放信息,简称RIFF格式。构成RIFF文件的基本单位称之为块(chunk)。每个RIFF文档是由若干个块构成。每个块(chunk)由块标识、块长度及数据等三部分所组成。其中,块标识保存的是由4个ASCII码字符组成的块名字。如不满4个字符则在右边以空格充填。块长度字段,WAV文件采用的是RIFF格式结构。至少是由3个块构成,分别是RIFF、fmt 和Data。所有基于压缩编码的WAV文件必须含有fact块。此外所有其它块都是可选的。块mt,Data及fact均为RIFF块的子块。WAV文件的文件格式类型标识符为“WAVE”。基本结构如表1。占4个字节,保存的是当前块数据的长度,不包括块标识和块长度字段。所以一个块的实际长度为块长度字段内的数值加8。RIFF格式规定,只有RIFF及LIST块可以含有子块,其它的块不允许包含子块。一个RIFF格式文档本身就是一个块。其前4个字节为文档标识“RIFF”,同时也是RIFF的块标识,标明该文档是一个有效的RIFF文档;第二部分为文件的数据长度,占4个字节,其数值为文件长度-8;第三部分为RIFF块数据,其中,前4个字节为文件格式类型标识,如:“WAVE”,“AVI”等,后面其它部分为RIFF块的子块。
WAV文件采用的是RIFF格式结构。至少是由3个块构成,分别是RIFF、fmt 和Data。所有基于压缩编码的WAV文件必须含有fact块。此外所有其它块都是可选的。块mt,Data及fact均为RIFF块的子块。WAV文件的文件格式类型标识符为“WAVE”。基本结构如表
RIFF块 |
文件格式类型“WAVE” |
fmt块 |
fact 块(压缩编码格式要含有该块) |
data块 |
WAV文件头数据结构定义:
struct RIFF_HEADER
{
char szRiffID[4]; // 'R','I','F','F'
unsigned int dwRiffSize;
char szRiffFormat[4]; // 'W','A','V','E'
};
struct WAVE_FORMAT
{
unsigned short wFormatTag;
unsigned short wChannels;
unsigned int dwSamplesPerSec;
unsigned int dwAvgBytesPerSec;
unsigned short wBlockAlign;
unsigned short wBitsPerSample;
};
struct FMT_BLOCK
{
char szFmtID[4]; // 'f','m','t',' '
unsigned int dwFmtSize;
struct WAVE_FORMAT wavFormat;
};
struct FACT_BLOCK
{
char szFactID[4]; // 'f','a','c','t'
unsigned int dwFactSize;
unsigned int dwData;
};
struct DATA_HEADER
{
char szDataID[4]; // 'd','a','t','a'
unsigned int dwDataSize;
};
typedef struct tWaveFile
{
FILE* m_pFile; // 采用WAV文件结构直接操作文件
HMMIO m_hmmio; // 采用Windows Multimedia API接口实现
MMCKINFO m_ckRIFF;
MMCKINFO m_ckData;
MMCKINFO m_ckFact;
DWORD m_dwSamples;
}WaveFile, *LPWaveFile;
1、创建WAV文件,返回LPWaveFile
#include <mmreg.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
static FOURCC g_fccRiff = mmioFOURCC('R', 'I', 'F', 'F');
static FOURCC g_fccWave = mmioFOURCC('W', 'A', 'V', 'E');
static FOURCC g_fccFmt = mmioFOURCC('f', 'm', 't', ' ');
static FOURCC g_fccFact = mmioFOURCC('f', 'a', 'c', 't');
static FOURCC g_fccData = mmioFOURCC('d', 'a', 't', 'a');
#define MM_SUCC(x) (MMSYSERR_NOERROR == (x))
#define MM_FAILED(x) (MMSYSERR_NOERROR != (x))
#define CHECK_MMERR(x) CHECK_BOOL(MM_SUCC(x))
#define CHECK_BOOL(x) {if (!(x)) return FALSE; }
void* CreateWavFile(const char * pcmFile, WAVEFORMATEX* wfex)
{
LPWaveFile pWavSource = (LPWaveFile)malloc(sizeof(WaveFile));
if (0)
{
// 采用Windows Multimedia API接口实现
HMMIO m_hmmio = mmioOpen((char*)pcmFile, NULL, MMIO_CREATE | MMIO_READWRITE | MMIO_EXCLUSIVE | MMIO_ALLOCBUF);
CHECK_BOOL(m_hmmio);
pWavSource->m_pFile = NULL;
pWavSource->m_hmmio = m_hmmio;
pWavSource->m_ckRIFF.fccType = g_fccWave;
pWavSource->m_ckRIFF.cksize = 0L;
CHECK_MMERR(mmioCreateChunk(pWavSource->m_hmmio, &pWavSource->m_ckRIFF, MMIO_CREATERIFF));
LONG lFmtSize = sizeof(WAVEFORMATEX) + wfex->cbSize;
MMCKINFO ckFmt;
ckFmt.ckid = g_fccFmt;
ckFmt.cksize = 0L;
CHECK_MMERR(mmioCreateChunk(m_hmmio, &ckFmt, 0));
CHECK_BOOL(lFmtSize == mmioWrite(pWavSource->m_hmmio, (HPSTR)wfex, lFmtSize));
CHECK_MMERR(mmioAscend(pWavSource->m_hmmio, &ckFmt, 0));
pWavSource->m_ckFact.ckid = g_fccFact;
pWavSource->m_ckFact.cksize = 0L;
CHECK_MMERR(mmioCreateChunk(pWavSource->m_hmmio, &pWavSource->m_ckFact, 0));
pWavSource->m_dwSamples = 0L;
CHECK_BOOL(sizeof(DWORD) == mmioWrite(pWavSource->m_hmmio, (HPSTR)&pWavSource->m_dwSamples, sizeof(DWORD)));
CHECK_MMERR(mmioAscend(pWavSource->m_hmmio, &pWavSource->m_ckFact, 0));
pWavSource->m_ckData.ckid = g_fccData;
pWavSource->m_ckData.cksize = 0L;
CHECK_MMERR(mmioCreateChunk(pWavSource->m_hmmio, &pWavSource->m_ckData, 0));
return pWavSource;
}
else
{
// 直接根据WAV文件结构,直接写文件
FILE* fp = NULL;
fopen_s(&fp, pcmFile, "wb");
if (fp)
{
pWavSource->m_hmmio = NULL;
pWavSource->m_pFile = fp;
pWavSource->m_dwSamples = 0;
//写WAV文件头
struct RIFF_HEADER rh;
memset(&rh, 0, sizeof(rh));
memcpy(rh.szRiffFormat, "WAVE", 4);
memcpy(rh.szRiffID, "RIFF", 4);// MAKEFOURCC('R', 'I', 'F', 'F');
fwrite(&rh, 1, sizeof(rh), fp);
struct FMT_BLOCK fb;
memcpy(fb.szFmtID, "fmt ", 4);
fb.dwFmtSize = sizeof(PCMWAVEFORMAT);
fb.wavFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;// WAVE_FORMAT_PCM;
fb.wavFormat.wChannels = wfex->nChannels;
fb.wavFormat.wBitsPerSample = wfex->wBitsPerSample;
fb.wavFormat.dwSamplesPerSec = wfex->nSamplesPerSec;
fb.wavFormat.wBlockAlign = fb.wavFormat.wChannels * fb.wavFormat.wBitsPerSample / 8;
fb.wavFormat.dwAvgBytesPerSec = fb.wavFormat.dwSamplesPerSec * fb.wavFormat.wBlockAlign;
fwrite(&fb, 1, sizeof(fb), fp);
struct DATA_HEADER dh;
memcpy(dh.szDataID, "data", 4);
dh.dwDataSize = 0;
fwrite(&dh, 1, sizeof(dh), fp);
return pWavSource;
}
}
return NULL;
}
2、将PCM数据写入文件
void WriteWavData(void* pSource, unsigned char * pPcmData, DWORD dwSize, WAVEFORMATEX* wfex)
{
LPWaveFile pWavSource = (LPWaveFile)pSource;
if (pWavSource)
{
if (dwSize > 0)
{
if (pWavSource->m_pFile)
{
fwrite(pPcmData, 1, dwSize*wfex->nBlockAlign, pWavSource->m_pFile);
}
else
{
mmioWrite(pWavSource->m_hmmio, (PCHAR)(pPcmData), dwSize*wfex->nBlockAlign);
}
pWavSource->m_dwSamples += (dwSize*wfex->nBlockAlign);
}
}
}
3、停止写入PCM数据,关闭文件
void CloseWavFile(void* pSource)
{
LPWaveFile pWavSource = (LPWaveFile)pSource;
if (pWavSource)
{
if (pWavSource->m_pFile)
{
//更新WAV文件dwRiffSize字段中的值
int nWhere = 4;
fseek(pWavSource->m_pFile, nWhere, SEEK_SET);
DWORD dwValue = sizeof(struct RIFF_HEADER) + sizeof(struct FMT_BLOCK) + sizeof(struct DATA_HEADER) + pWavSource->m_dwSamples - 8;
fwrite(&dwValue, 1, sizeof(DWORD), pWavSource->m_pFile);
//更新WAV文件DataChunk中Size字段的值
nWhere = sizeof(struct RIFF_HEADER) + sizeof(struct FMT_BLOCK) + 4/*sizeof(DATA_HEADER::szDataID)*/;
fseek(pWavSource->m_pFile, nWhere, SEEK_SET);
fwrite(&pWavSource->m_dwSamples, 1, sizeof(DWORD), pWavSource->m_pFile);
fclose(pWavSource->m_pFile);
pWavSource->m_pFile = NULL;
}
else
{
mmioAscend(pWavSource->m_hmmio, &pWavSource->m_ckData, 0);
mmioAscend(pWavSource->m_hmmio, &pWavSource->m_ckRIFF, 0);
mmioSeek(pWavSource->m_hmmio, pWavSource->m_ckFact.dwDataOffset, SEEK_SET);
mmioWrite(pWavSource->m_hmmio, (HPSTR)&pWavSource->m_dwSamples, sizeof(DWORD));
mmioClose(pWavSource->m_hmmio, 0);
}
free(pSource);
pSource = NULL;
}
}
至此,一个完整的PCM文件创建和写入过程就算完结了。整个代码中,通过m_pFile和m_hmmio为空,来区别是哪种方式来对PCM文件进行读写。