c++实现waveOutOpen音频播放功能

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xbk123123/article/details/53787287

上一节我们简单的解析了Windows下实现录音功能,本节我们讲解一下,怎样实现音频播放功能,以及实现时应该注意的问题。

这里我们注意:现在我们将的录音和音频都是基于PCM格式的(就是未压缩格式的),这种未经过压缩的音频文件,占用的存储空间非常大,不利于网络的传输,因此在后面我们将逐步的讲解,采用ACM算法,实现音频的解压缩。

好了,我们言归正传,接着讲解音频的播放。音频播放的大概流程是:从音频文件中读出音频数据,将这些数据送入声卡,驱动声卡进行播放。具体的播放过程如下(这里我们讲解最简单的类似于playSound的实现原理):

1)定义音频流相关设备信息(WAVEFORMAT结构体):这一步和录音时相同的。代码如下;

BOOL CIP_PHONEDlg::initAudioDevice()
{
	try
	{
		waveForm.nSamplesPerSec = 44100; /* sample rate */
		waveForm.wBitsPerSample = 16; /* sample size */
		waveForm.nChannels= 2; /* channels*/
		waveForm.cbSize = 0; /* size of _extra_ info */
		waveForm.wFormatTag = WAVE_FORMAT_PCM;
		waveForm.nBlockAlign = (waveForm.wBitsPerSample * waveForm.nChannels) >> 3;
		waveForm.nAvgBytesPerSec = waveForm.nBlockAlign * waveForm.nSamplesPerSec;
		return TRUE;
	}
	catch(...)
	{
		return FALSE;
	}
}

2)启动音频输出设备,使用Waveoutopen函数。
if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveForm, 0, 0, CALLBACK_NULL) !=MMSYSERR_NOERROR)
{
	::AfxMessageBox("音频设备打卡失败",MB_OK);
	return FALSE;
}
这个函数需要一个接受输出音频设备的句柄HWAVEOUT对象。

3)、获得需要播放的音频文件大小和数据。这里我们在读取文件时,一次性读取所有的文件内容(这样就限制了他只能播放小的文件,类似于playsound函数)

LPSTR loadAudioBlock(const char*filename, DWORD* blockSize)
{
	HANDLE hFile= INVALID_HANDLE_VALUE;
	DWORD size = 0;
	DWORD readBytes = 0;
	void* block = NULL;
	if((hFile = CreateFile(filename,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL)) == INVALID_HANDLE_VALUE)
	{
		return NULL;
	}
	do 
	{
		if((size = GetFileSize(hFile, NULL)) ==0)
		{
			break;
		}
		if((block = HeapAlloc(GetProcessHeap(),0, size)) == NULL)
		{
			break;
		}
		ReadFile(hFile, block, size,&readBytes, NULL);
	} while(0);
	CloseHandle(hFile);
	*blockSize = size;
	return (LPSTR)block;
}
4)当获得音频数据和大小之后,驱动声卡播放音频,通过调用waveOutWrite函数实现播放

void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
	WAVEHDR header;
	ZeroMemory(&header, sizeof(WAVEHDR));
	header.dwBufferLength = size;
	header.lpData = block;
	waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
	waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
	Sleep(500);
	while(waveOutUnprepareHeader(hWaveOut,&header,sizeof(WAVEHDR)) ==WAVERR_STILLPLAYING)
	Sleep(100);
}

这里注意下面几点:

1、初始化音频缓冲区头信息,着一步和录音是相同的

header.dwBufferLength = size;
header.lpData = block;

2、将头信息加入播放设备中

waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));

3、播放音频

waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));

4、每播放完一个数据块,将该数据块释放,直到所有的音频文件播放完成

while(waveOutUnprepareHeader(hWaveOut,&header,sizeof(WAVEHDR)) ==WAVERR_STILLPLAYING)

通过上面的几步就可以将音频播放出来。

5)音频播放结束后,调用waveOutClose函数关闭音频设备


其实我们可以看到播放和录音的操作步骤以及函数基本上是想回对应的。

具体的代码:

class CIP_PHONEDlg : public CDialog
{
private:
	BOOL initAudioDevice();//初始话音频结构体
	BOOL playSounds(char* soundFileName);//播放音频
public:
	WAVEFORMATEX waveForm;//采集声音的格式,结构体
};

void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
	WAVEHDR header;
	ZeroMemory(&header, sizeof(WAVEHDR));
	header.dwBufferLength = size;
	header.lpData = block;
	waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
	waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
	Sleep(500);
	while(waveOutUnprepareHeader(hWaveOut,&header,sizeof(WAVEHDR)) ==WAVERR_STILLPLAYING)
	Sleep(100);
}
LPSTR loadAudioBlock(const char*filename, DWORD* blockSize)
{
	HANDLE hFile= INVALID_HANDLE_VALUE;
	DWORD size = 0;
	DWORD readBytes = 0;
	void* block = NULL;
	if((hFile = CreateFile(filename,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL)) == INVALID_HANDLE_VALUE)
	{
		return NULL;
	}
	do 
	{
		if((size = GetFileSize(hFile, NULL)) ==0)
		{
			break;
		}
		if((block = HeapAlloc(GetProcessHeap(),0, size)) == NULL)
		{
			break;
		}
		ReadFile(hFile, block, size,&readBytes, NULL);
	} while(0);
	CloseHandle(hFile);
	*blockSize = size;
	return (LPSTR)block;
}

BOOL CIP_PHONEDlg::playSounds(char* soundFileName)
{
	if (soundFileName == NULL)
	{
		return FALSE;
	}
	if (!initAudioDevice())
	{
		return FALSE;
	}
	HWAVEOUT hWaveOut; 
	LPSTR block;
	DWORD blockSize;
	
	if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveForm, 0, 0, CALLBACK_NULL) !=MMSYSERR_NOERROR)
	{
		::AfxMessageBox("音频设备打卡失败",MB_OK);
		return FALSE;
	}
	if((block = loadAudioBlock(soundFileName, &blockSize)) ==NULL)
	{
		::AfxMessageBox("无法打开音频文件",MB_OK);
		return FALSE;
	}
	writeAudioBlock(hWaveOut, block, blockSize); 
	waveOutClose(hWaveOut);
	return TRUE;
}
到此为止,我们已经将音频的录音和播放讲解完成,但是这些代码都是针对于pcm裸流的操作。裸流不仅占用的内存空间大,而且很不利于网络传输,因此在下一节我们将讲解pcm裸流的压缩技术,即通过ACM压缩音频文件。

展开阅读全文
博主设置当前文章不允许评论。

没有更多推荐了,返回首页