VC++设计的DirectSound播放声音程序学习小记
2013-07-26 17:09
531 查看
最近在尝试使用c++编写一个简单的DirectSound播放器,仅播放WAV格式的声音文件,主要是实现在线播放流数据的功能。
首先,介绍一下程序的设计,利用DirectSound播放声音,需要以下几个步骤:
1)创建并初始化一个支持IDirectSound8接口的对象;
2)设置应用程序与声音设备的协作级别;
3)创建一个声音缓存对象;
4)获取IDirectSoundBuffer8接口指针;
5)设置缓存提醒机制;
6)创建播放线程;
7)随时更新缓存数据。
下面就每步具体来说。
1、创建并初始化一个支持IDirectSound8接口的对象
每一个DirectSound对象都是与声音硬件设备(声卡)相关联的,其中的m_pDS则返回操作DS设备对象所需使用的COM接口IDirectSound8。通过调用该接口的各种方法,我们可以生成缓冲区对像、获取和设置设备对象的属性等。
2、设置应用程序与声音设备的协作级别
协作级别用以保证多个应用程序之间,不会在错误的时间以错误的方式获得设备的使用权。DSSCL_PRIORITY,顾名思义,获得的是优先的协作级别。
3、创建一个声音缓存对象
5、设置缓存提醒机制
这是针对流播放而编写,其关键的四个步骤:
首先,确保缓冲区已做好接收新数据的准备。第二,锁定需要写入数据的缓冲区域。第三,将数据拷贝进锁定的区域。最后,解锁缓冲区域。但现在会面临下一个问题,那就是怎么保证播放前需要播放的数据已经写完呢?就是采用提醒机制。
6、创建播放线程
7、随时更新缓存数据
以上是在参考书本例程的基础上自己总结的,其实DS播放声音的过程并不复杂,问题是要实现在线播放,要求我们能实时的将需要播放的数据放入播放的缓存当中,这里我们考虑可以在第7步当中,将wavefile的读取函数,改为自己的写数据函数,将一定字节数的数据写到缓存里面,同时修改文件结束字节计数的大小,使得我们的数据在播放完成以前,不会使播放线程结束,当然,需要调整一下缓冲区的大小来防止声音的播放出现间断。
首先,介绍一下程序的设计,利用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的读取函数,改为自己的写数据函数,将一定字节数的数据写到缓存里面,同时修改文件结束字节计数的大小,使得我们的数据在播放完成以前,不会使播放线程结束,当然,需要调整一下缓冲区的大小来防止声音的播放出现间断。
相关文章推荐
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放
- 用VC实现声音(wav)播放的小程序
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放
- 建立第一个directX程序--winform--在C#下利用DirectSound实现声音播放
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放
- 如何在VC++程序中添加播放声音模块——PlaySound函数的使用
- 201521123110《Java程序与设计》第13周学习总结
- VC程序播放音乐必备---mciSendString用法
- VC运用ADO设计数据库程序
- 孙鑫VC学习(第1课--Windows程序内部运行原理)
- VC++程序中加入自定义声音(PlaySound函数用法)
- VC++中播放声音
- VC++程序中用PlaySound函数加入自定义声音
- 程序设计实践与提高2 - 学习记录
- VC中使用低级音频函数WaveX播放声音文件
- 用DirectSound在窗口中播放声音,可当窗口失去焦点后却不播放的解决办法
- 小程序学习心得之音乐播放
- VC中利用MFC设计绘图程序初步
- [小插曲]VC学习——基于MFC的模拟时钟程序