VC++设计的DirectSound播放声音程序学习小记

最近在尝试使用c++编写一个简单的DirectSound播放器,仅播放WAV格式的声音文件,主要是实现在线播放流数据的功能。

首先,介绍一下程序的设计,利用DirectSound播放声音,需要以下几个步骤:

1)创建并初始化一个支持IDirectSound8接口的对象;

2)设置应用程序与声音设备的协作级别;

3)创建一个声音缓存对象;

4)获取IDirectSoundBuffer8接口指针;

5)设置缓存提醒机制;

6)创建播放线程;

7)随时更新缓存数据。

下面就每步具体来说。

1、创建并初始化一个支持IDirectSound8接口的对象

hr = DirectSoundCreate8(lpGUID, &m_pDS, NULL);
	if(FAILED(hr))	  
	{
		DXTRACE_ERR(TEXT("DirectSoundCreate8"), hr);
		return;
	}

每一个DirectSound对象都是与声音硬件设备(声卡)相关联的,其中的m_pDS则返回操作DS设备对象所需使用的COM接口IDirectSound8。通过调用该接口的各种方法,我们可以生成缓冲区对像、获取和设置设备对象的属性等。

2、设置应用程序与声音设备的协作级别

hr = m_pDS->SetCooperativeLevel(m_hWnd, DSSCL_PRIORITY);
	if(FAILED(hr))	  
	{
		DXTRACE_ERR(TEXT("SetCooperativeLevel"), hr);
		return;
	}

协作级别用以保证多个应用程序之间,不会在错误的时间以错误的方式获得设备的使用权。DSSCL_PRIORITY,顾名思义,获得的是优先的协作级别。

3、创建一个声音缓存对象

ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 |
		DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY;               
	dsbd.dwBufferBytes = BUFFER_NOTIFY_SIZE * 4;          
	dsbd.lpwfxFormat = &m_waveFile.m_format;
	hr = m_pDS->CreateSoundBuffer(&dsbd, &lpDSBuffer, NULL);
	if(FAILED(hr))
	{
		DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);
		return;
	}
这步之前,有一个打开声音文件的过程,具体使用什么方法,我们可以有自己的习惯,可以使用常规的文件操作函数fopen,也可以使用API函数MMIO来打开,这里使用自定义的CWaveFile类打开。这段代码里面的dsbd是一个DSBUFFERDESC结构体,用以存储缓存对象的一些属性。下面是CWaveFile类的源代码。

/ WaveFile.cpp: CWaveFile类的实现
//
//

#include "stdafx.h"
#include "WaveFile.h"

//
// Construction/Destruction
//

CWaveFile::CWaveFile()
{
	Init();
}

CWaveFile::~CWaveFile()
{
}

void CWaveFile::Init()
{
	m_hParentWnd = NULL;
	m_pWavData = NULL;
	m_dwOffset = 0;
	m_dwDataSize = 0;
}

void CWaveFile::Release()
{
	if(m_pWavData)
	{
		delete []m_pWavData;
	}
}

BOOL CWaveFile::OpenWave(LPTSTR pszWavPath, HWND hWnd /*= NULL*/ )
{
	MMRESULT mmResult = 0;
	DWORD dwFmtSize = 0;

	Release(); // Release buffers
	Init(); // Init local variables

	m_hWaveFile = mmioOpen(pszWavPath, NULL, MMIO_READ);
	if(m_hWaveFile == NULL)
	{
		MessageBox(m_hParentWnd, "Open the wav file failed", "Open", MB_ICONERROR);
		return FALSE;
	}
	
	// Descend 'wave' chunk
	m_mmckinfoParent.fccType = mmioFOURCC('W','A','V','E');
	mmResult = mmioDescend(m_hWaveFile, &m_mmckinfoParent, NULL, MMIO_FINDRIFF);
	if(mmResult)
	{
		MessageBox(m_hParentWnd, "It's not a wave format file", "Open", MB_ICONERROR);
		return FALSE;
	}
	
	// Descend 'fmt ' chunk
	m_mmckinfoSubChunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
	mmResult = mmioDescend(m_hWaveFile, &m_mmckinfoSubChunk, &m_mmckinfoParent, MMIO_FINDCHUNK);
	if(mmResult)
	{
		MessageBox(m_hParentWnd, "Can't find the fmt chunk", "Open", MB_ICONWARNING);
		return FALSE;
	}
	
	// Read 'fmt ' chunk
	dwFmtSize = m_mmckinfoSubChunk.cksize;
	if((unsigned long)mmioRead(m_hWaveFile, (HPSTR)&m_format, dwFmtSize) != dwFmtSize)
	{
		MessageBox(m_hParentWnd, "Read format chunk failed", "Open", MB_ICONWARNING);
		return FALSE;
	}
	
	// Ascend the 'fmt ' chunk
	mmResult = mmioAscend(m_hWaveFile, &m_mmckinfoSubChunk, 0);
	if(mmResult)
	{
		MessageBox(m_hParentWnd, "Ascend fmt chunk failed", "Open", MB_ICONWARNING);
		return FALSE;
	}
	
	// Descend the 'data' chunk
	m_mmckinfoSubChunk.ckid = mmioFOURCC('d','a','t','a');
	mmResult = mmioDescend(m_hWaveFile, &m_mmckinfoSubChunk, &m_mmckinfoParent, MMIO_FINDCHUNK);
	if(mmResult)
	{
		MessageBox(m_hParentWnd, "Cannot find data chunk", "Open", MB_ICONWARNING);
		return FALSE;
	}
	else
	{
		// Point to the start position of wave data
		m_dwDataSize = m_mmckinfoSubChunk.cksize;

		// Offset byte of 'data' chunk from the start of file
		m_dwOffset = mmioSeek(m_hWaveFile, 0, SEEK_CUR);
	}
	
	return TRUE;
}

void CWaveFile::ResetFile()
{
	if(m_hWaveFile)
	{
		// Reset file pointer to beginning of 'data' chunk
		mmioSeek(m_hWaveFile, m_dwOffset, SEEK_SET);
	}
}

void CWaveFile::Read(LPBYTE pData, DWORD dwReadBytes, DWORD& dwActuallyRead)
{
	if(m_hWaveFile)
	{
		// Read a specified number of bytes from current opened file
		dwActuallyRead = mmioRead(m_hWaveFile, (HPSTR)pData, dwReadBytes);
	}
}
4、获取IDirectSoundBuffer8接口指针

hr = lpDSBuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&m_pDSBuffer8);
	if(FAILED(hr))
	{
		DXTRACE_ERR(TEXT("QueryInterface"), hr);
		return;
	}
	lpDSBuffer->Release();

5、设置缓存提醒机制

这是针对流播放而编写,其关键的四个步骤:

首先,确保缓冲区已做好接收新数据的准备。第二,锁定需要写入数据的缓冲区域。第三,将数据拷贝进锁定的区域。最后,解锁缓冲区域。但现在会面临下一个问题,那就是怎么保证播放前需要播放的数据已经写完呢?就是采用提醒机制。

for(int i = 0; i < BUF_ZONE_NUM; i++)
	{
		m_hEvent[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
		DSBPosNotify[i].dwOffset = (i + 1) * BUFFER_NOTIFY_SIZE - 1;
		DSBPosNotify[i].hEventNotify = m_hEvent[i];                          
	}
	hr = m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&m_pDSNotify);
	if(FAILED(hr))
	{
		DXTRACE_ERR(TEXT("QueryInterface"), hr);
		return;
	}
	m_pDSNotify->SetNotificationPositions(BUF_ZONE_NUM, DSBPosNotify);
	m_pDSNotify->Release();
上面的代码采用了4个通知点,将缓冲区划分为4段,每个分段点使用CreateEvent函数生成一个事件对象,用以供播放线程接收通知信号。

6、创建播放线程

m_threadHandle = CreateThread(0, 0, PlayThread, this, CREATE_SUSPENDED, NULL);
	if(!m_threadHandle)
	{
		MessageBox(m_hWnd, "CreateThread failed", "Play", MB_ICONERROR);
		return;
	}
	else
	{
		//设置正在播放标志位
		m_bPlaying = TRUE;

		// 播放之前初始化声音缓冲区数据
		dwLockBytes = BUF_ZONE_NUM * BUFFER_NOTIFY_SIZE;
		if(dwLockBytes >= m_dwLeftCount)
		{
			dwLockBytes = m_dwLeftCount;
		}
		hr = m_pDSBuffer8->Lock(0, dwLockBytes, &lplockbuf, &dwAudioBytes1, NULL, NULL, 0);  //偏移地址为0,锁定长度为dwLockBytes
		if(FAILED(hr))
		{
			DXTRACE_ERR(TEXT("Lock"), hr);
			return;
		}
		m_waveFile.Read((BYTE*)lplockbuf, dwAudioBytes1, dwActualRead);        //MMIO函数从声音文件读取数据
		hr = m_pDSBuffer8->Unlock(lplockbuf, dwAudioBytes1, NULL, 0);          //解锁
		if(FAILED(hr))
		{
			DXTRACE_ERR(TEXT("Unlock"), hr);
			return;
		}

		// Init position pan and volume
		// Then start playing
		m_pDSBuffer8->SetCurrentPosition(0);
		m_pDSBuffer8->SetPan(m_lPan);
		m_pDSBuffer8->SetVolume(m_lVolume);
		m_pDSBuffer8->Play(0, 0, DSBPLAY_LOOPING);
		m_dwNextWritePos = 0;
		m_dwLeftCount -= dwLockBytes;             

		// Resume thread for notification position
		ResumeThread(m_threadHandle);
	}
采用线程的方式播放音乐。

7、随时更新缓存数据

m_pDSBuffer8->Lock(m_dwNextWritePos, // 锁开始点到缓存起始地址的偏移量
		dwLockSize, //需要锁定的缓存区域的大小
		&pDSLockedBuffer, // Point to the first locked part of the buffer
		&dwDSLockedBufferSize, // Number of bytes in the block at Ptr1
		&pDSLockedBuffer2, // Point to the second locked part of the buffer
		&dwDSLockedBufferSize2, // Number of bytes in the block at Ptr2
		0
	);
	
	if(hr == DSERR_BUFFERLOST)
	{
		m_pDSBuffer8->Restore(); // Restore the memory allocation for a lost sound buffer
		m_pDSBuffer8->Lock(m_dwNextWritePos, // Offset from the start of the buffer to the point where the lock begins
			dwLockSize, // Size of the portion of the buffer to lock
			&pDSLockedBuffer, // Point to the first locked part of the buffer
			&dwDSLockedBufferSize, // Number of bytes in the block at Ptr1
			&pDSLockedBuffer2, // Point to the second locked part of the buffer
			&dwDSLockedBufferSize2, // Number of bytes in the block at Ptr2
			0
		);
	}

	if(SUCCEEDED(hr))
	{
		//从波形文件中读取数据至第一个锁定缓存中
		m_waveFile.Read((BYTE*)pDSLockedBuffer, 
			dwDSLockedBufferSize,
			dwBytesRead
		);

		// 计算下一个需要加锁的偏移量
		m_dwNextWritePos += dwBytesRead;
		m_dwLeftCount -= dwBytesRead;
		
		//如果第二个指针非空
		if(pDSLockedBuffer2 != NULL) 
		{
			//从波形文件中读取数据至第二个锁定缓存中
			m_waveFile.Read((BYTE*)pDSLockedBuffer2, 
				dwDSLockedBufferSize2,
				dwBytesRead
			);

			//计算下一个需要加锁的偏移量
			m_dwNextWritePos += dwBytesRead;
			m_dwLeftCount -= dwBytesRead;
		}

		//环形缓冲
		m_dwNextWritePos %= (BUFFER_NOTIFY_SIZE * BUF_ZONE_NUM);

		//释放锁定的声音缓存
		hr = m_pDSBuffer8->Unlock(pDSLockedBuffer, 
			dwDSLockedBufferSize,
			pDSLockedBuffer2, 
			dwDSLockedBufferSize2
		);
	}
具体步骤在第5步已经叙述,代码大家自己看吧。


以上是在参考书本例程的基础上自己总结的,其实DS播放声音的过程并不复杂,问题是要实现在线播放,要求我们能实时的将需要播放的数据放入播放的缓存当中,这里我们考虑可以在第7步当中,将wavefile的读取函数,改为自己的写数据函数,将一定字节数的数据写到缓存里面,同时修改文件结束字节计数的大小,使得我们的数据在播放完成以前,不会使播放线程结束,当然,需要调整一下缓冲区的大小来防止声音的播放出现间断。






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Direct3D是微软公司推出的一套用于图形和动画程序设计的应用程序接口(API),主要用于Windows操作系统环境下的图形渲染。精通Direct3D图形及动画程序设计意味着对Direct3D的原理和使用方法有深入的理解,并能够熟练地利用该技术进行图形和动画程序的开发。 在精通Direct3D图形及动画程序设计方面,首先需要对计算机图形学的基本原理有一定的了解,包括三维坐标系、光照模型、常用的渲染算法等。此外,深入理解Direct3D的渲染流水线原理也是非常重要的,了解顶点着色器、像素着色器、纹理映射等关键概念和相关技术。 精通Direct3D图形及动画程序设计还需要掌握Direct3D的编程接口和相关工具,例如用于创建和管理Direct3D设备的接口、顶点缓冲区和索引缓冲区的使用、纹理和着色器的加载与应用等。此外,在动画程序设计方面,还需要熟悉动画的关键帧原理和插值算法,掌握动画控制器的使用。 对于精通Direct3D图形及动画程序设计的人员来说,能够从零开始设计和实现高效的图形和动画程序,并能够解决常见的性能和质量问题。此外,也能够对已有的程序进行优化和改进,提升程序的渲染效果和用户体验。 总之,精通Direct3D图形及动画程序设计需要对计算机图形学和Direct3D技术有深入的理解,掌握相关编程接口和工具,能够独立设计和实现图形和动画程序,解决常见问题,并能够对程序进行优化和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值