DSound音频播放

DSound是directx中的用来处理声音特性的一部分,播放音频需要准备的工作其实和waveout也是差不多啊,创建directxobject也就是创建一个LPDIRECTSOUND,这个结构的定义可以参考dsound.h头文件,接着需要一个声音的描述WAVEFORMATEX,这个声音的描述其实在DSound中是位于buffer里面的,DSound对WAVEFORMATEX进行了封装,具体可以参考LPDIRECTSOUNDBUFFER结构的定义,同样是位于dsound.h中。播放音频需要一个窗口或者子窗口的句柄,否则播放不了,没有去很仔细的研究,只是实际遇到了所以提一下。

DSound的音频播放只需要使用初始化好的LPDIRECTSOUNDBUFFER进行控制,直接调用LPDIRECTSOUNDBUFFER的Play、Stop即可。需要涉及到的操作是buffer的Lock、填充数据、Unlock,这里的操作和WaveOut是类似的。和WaveOut一样在使用流播放的时候,我们还是分段缓冲,这样减少加载的时间间隔减少噪音。

DSound的缓冲的做饭时直接设置一块大的buffer,然后将这段buffer分段锁定然后填充数据解锁播放达到缓冲的效果。但在DSound中并不使用Waveout的回调函数提示播放完成,其实是有一个更加方便的办法。在LPDIRECTSOUNDBUFFER中我们是可以设置播放进度notify的,比回调好用多了。将buffer分段之后设置notify的偏移位置当播放到这个点的时候就会发送一个消息,捕获它完成相应的操作即可。可以使用注册buffer的com接口来获得notify。创建一个线程不断的捕获buffer发过来的播放进度notify,然后加载数据到指定的buffer空闲区。

具体看代码描述,下面是头文件的定义

//buffer的分段的个数
#define BUFFERCOUNT 2
//每段buffer的大小
#define BUFFERNOTIFYSIZE (BLOCKSIZE)

class CDxSound
{
public:
	CDxSound(HWND hWnd, char* pFileName);
	~CDxSound(void);

	int Play();
private:
	int Initialize(void);
	int LoadAudioFormat(void);
	void ProcessBuffer();
	static DWORD WINAPI PlayThread(LPVOID lpParame);

	HWND m_hWnd;
	CWavInfo *m_pWavInfo;

	LPDIRECTSOUND m_pDirectObject;		//dx objecct
	WAVEFORMATEX m_stWfx;				//wav format
	LPDIRECTSOUNDBUFFER m_pDSoundBuffer;//dx buffer
	DSBUFFERDESC m_stBufferDes;			//buffer desc
	LPDIRECTSOUNDNOTIFY m_pDSNotify;
	DSBPOSITIONNOTIFY m_pPosNotify[BUFFERCOUNT];
	HANDLE m_hEvent[BUFFERCOUNT];
	DWORD m_dwNextWriteOffset;
	int m_nIsPlaying;
};
函数等和上一篇的WaveOut是类似的。

DSound的初始化以及声音buffer的描述

int CDxSound::Initialize(void)
{
	int i;

	for(i = 0; i < BUFFERCOUNT; ++i)
	{
		m_hEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
	}

	//创建directsound类型
	if(DirectSoundCreate(NULL, &m_pDirectObject, NULL) == DS_OK)
	{
		m_pDirectObject->SetCooperativeLevel(m_hWnd, DSSCL_PRIORITY);
	}
	else
	{
		return -1;
	}
	if(LoadAudioFormat() != 0)
	{
		return -1;
	}

	//声音的描述以及声音buffer的描述
	memset(&m_stBufferDes, 0, sizeof(DSBUFFERDESC));
	m_stBufferDes.lpwfxFormat   = &m_stWfx;
	m_stBufferDes.dwFlags       = DSBCAPS_GLOBALFOCUS        | 
								  DSBCAPS_CTRLPOSITIONNOTIFY |
								  DSBCAPS_GETCURRENTPOSITION2;
	m_stBufferDes.dwSize        = sizeof(DSBUFFERDESC);
	m_stBufferDes.dwBufferBytes = BUFFERCOUNT * BUFFERNOTIFYSIZE;
	
	LPDIRECTSOUNDBUFFER lpbuffer;
	if(m_pDirectObject->CreateSoundBuffer(
		&m_stBufferDes, &lpbuffer, 0) != DS_OK)
	{
		return -1;
	}

	//注册这个dsoundbuffer的com接口
	if(lpbuffer->QueryInterface(IID_IDirectSoundBuffer, (LPVOID*)&m_pDSoundBuffer) != DS_OK)
	{
		return -1;
	}
	lpbuffer->Release();
	
	for(i = 0; i < BUFFERCOUNT; ++i)
	{
		m_pPosNotify[i].dwOffset = i * BLOCKSIZE;
		m_pPosNotify[i].hEventNotify = m_hEvent[i];
	}

	if(m_pDSoundBuffer->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&m_pDSNotify) != DS_OK)
	{
		return -1;
	}
	//设置播放进度notify
	if(m_pDSNotify->SetNotificationPositions(BUFFERCOUNT, m_pPosNotify) != DS_OK)
	{
		return -1;
	}
	m_pDSNotify->Release();

	return 0;
}
不要以为上面的LPDIRECTSOUNDBUFFER lpbuffer这个没用,其实是不同的两个东西,对com不熟悉,不知道里面完成了什么。希望大神解释一下~

播放线程,不断的检查播放进度notify进行数据的填充。

DWORD WINAPI CDxSound::PlayThread(LPVOID lpParame)
{
	DWORD res;
	DWORD len;
	unsigned int dwWrite;
	LPVOID lplockbuf;
	CDxSound *dxSound = (CDxSound*)lpParame;

	dxSound->m_pDSoundBuffer->Lock(0, BUFFERNOTIFYSIZE, &lplockbuf, &len, NULL, NULL, DSBLOCK_ENTIREBUFFER);
	dxSound->m_pWavInfo->ReadABlockData(lplockbuf, &dwWrite);
	dxSound->m_pDSoundBuffer->Unlock(lplockbuf, len, NULL, 0);
	dxSound->m_pDSoundBuffer->SetCurrentPosition(0);
	dxSound->m_pDSoundBuffer->Play(0, 0, DSBPLAY_LOOPING);
	dxSound->m_dwNextWriteOffset = BUFFERNOTIFYSIZE;

	//一直读取数据播放直到文件结束
	while(dxSound->m_nIsPlaying == 1)
	{
		//等待dsound的播放进度信号(这里设置了BUFFERCOUNT个播放进度信号),
		//如果捕获到信号那么读取数据到播放完的那一段缓冲区中
		res = WaitForMultipleObjects(BUFFERCOUNT, dxSound->m_hEvent, false, INFINITE);
		if(res >= WAIT_OBJECT_0)
		{
			//读取数据到指定缓冲区
			dxSound->ProcessBuffer();
		}
	}
	return 0;
}
数据填充函数:

void CDxSound::ProcessBuffer(void)
{
	DWORD dwBytesWrittenToBuffer = 0;
	void* pDSLockedBuffer = NULL;
	DWORD dwDSLockedBufferSize;
	unsigned int nSize;

	//锁定播放过完的一个BUFFERNOTIFYSIZE的缓冲区区域进行数据拷贝
	m_pDSoundBuffer->Lock(m_dwNextWriteOffset, BUFFERNOTIFYSIZE,
		&pDSLockedBuffer, &dwDSLockedBufferSize,
		NULL, NULL, 0);

	//读取数据,并检查是否到达文件结束位置
	if(m_pWavInfo->ReadABlockData(pDSLockedBuffer, &nSize) != 0)
	{
		//文件结束,释放上面锁定的区域,播放结束
		m_pDSoundBuffer->Unlock(pDSLockedBuffer, dwDSLockedBufferSize, NULL, NULL);
		m_pDSoundBuffer->Stop();
		m_nIsPlaying = 0;
	}

	//设置下一次锁定的缓冲区位置
	m_dwNextWriteOffset += nSize;
	m_dwNextWriteOffset = m_dwNextWriteOffset % (BUFFERNOTIFYSIZE * BUFFERCOUNT);
	m_pDSoundBuffer->Unlock(pDSLockedBuffer, dwDSLockedBufferSize, NULL, NULL);
}
最后是播放函数,主要是完成线程的启动还有消息的处理避免界面死掉:

int CDxSound::Play(void)
{
	if(m_pWavInfo == NULL)
	{
		return -1;
	}
	m_nIsPlaying = 1;

	//创建播放线程
	HANDLE pHandle;
	pHandle = CreateThread(0, 0, PlayThread, this, NULL, NULL);
	if(pHandle == NULL)
	{
		return -2;
	}

	DWORD result; 
	MSG msg; 
	while(true)
	{
		//接收所有信号,避免主线程死掉
		result = MsgWaitForMultipleObjects(1, &pHandle, false, INFINITE, QS_ALLINPUT); 
		if(result == (WAIT_OBJECT_0))
		{
			break;
		}//子线程结束返回
		else 
		{ 
			PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
			DispatchMessage(&msg); 
		}//其他消息或者信号
	}
	return 0;
}
完毕~~

As I am a beginner with DSound, please tell me if there are any mistakes in this article!


猜你喜欢

转载自blog.csdn.net/T20091/article/details/17028237