VC++ PCM数据保存为WAV声音文件

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”。基本结构如表

WAV文件结构
  
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文件进行读写。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值