您的位置:首页 > 编程语言 > C语言/C++

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

2013-07-26 17:09 531 查看
最近在尝试使用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的读取函数,改为自己的写数据函数,将一定字节数的数据写到缓存里面,同时修改文件结束字节计数的大小,使得我们的数据在播放完成以前,不会使播放线程结束,当然,需要调整一下缓冲区的大小来防止声音的播放出现间断。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  VC++ 播放器 DirectSound