3.3Using WAV Data
在WDM驱动模式下,DirectSound缓冲区支持如下WAV格式:多声道,多个扬声器配置,例如5.1,在前左,前中,前右,后左,后右都有扬声器,超重低音。也支持多于16的采样精度。
这种格式可以用WAVEFORMATEXTENSIBLE结构来描述,这个结构是WAVEFORMATEX的扩展,
对于多声道,DirectSound并不支持3D。
下面我讲一下如何计算wave的播放时间
Wave格式的播放长度由数据大小和格式决定,可以通过CWaveFile::GetSize和CWaveFile::GetFormat来获取数据的大小和格式
下面的代码告诉你如何计算wave的总播放时间以毫秒为时间单位
DWORD GetSoundLength(LPSTR strFileName)
{
CWaveFile* pWav;
DWORD dwLen = 0;
DWORD dwSize;
WAVEFORMATEX* wfx;
pWav = new CWaveFile();
if (SUCCEEDED(pWav->Open(strFileName, NULL, WAVEFILE_READ)))
{
wfx = pWav->GetFormat();
dwSize = pWav->GetSize();
dwLen = (DWORD) (1000 * dwSize / wfx->nAvgBytesPerSec);
pWav->Close();
}
if (pWav) delete pWav;
return dwLen;
}
3.43-D Sound
3.5增加声音特技Using Effects
DirectX通过DirectX Media Objects(DMOs)来支持在声音中添加特技。
为了使用特技,DirectSound 应用程序必须首先CoInitialize来初始化Com库,当然也不排除通过DirectSoundCreate8来创建设备对象。
对于在DSBUFFERDESC结构中设置DSBCAPS_CTRLFX标志的辅助缓冲区,你可以在该缓冲区添加任意的特技,但是,buffer一定要处于停止状态并且没有被锁定,在DiectX 9.0中,特技不依靠硬件来加速。当然也可以在硬件缓冲区中设置特技,但这样做没有任何的好处。
对于很小的缓冲区,特技也许并不能好好工作,DirectSound不提倡在小于150毫秒的数据缓冲区中使用特技,这个值被定义为DSBSIZ_FX_MIN。
下面的代码在缓冲设置了回声特技。
HRESULT SetEcho(LPDIRECTSOUNDBUFFER8 pDSBuffer)
{
HRESULT hr;
DWORD dwResults[1]; // One element for each effect.
// Describe the effect.
DSEFFECTDESC dsEffect;
memset(&dsEffect, 0, sizeof(DSEFFECTDESC));
dsEffect.dwSize = sizeof(DSEFFECTDESC);
dsEffect.dwFlags = 0;
dsEffect.guidDSFXClass = GUID_DSFX_STANDARD_ECHO; //设置特技
// Set the effect
if (SUCCEEDED(hr = pDSBuffer->SetFX(1, &dsEffect, dwResults)))
{
switch (dwResults[0])
{
case DSFXR_LOCHARDWARE:
OutputDebugString("Effect was placed in hardware.");
break;
case DSFXR_LOCSOFTWARE:
OutputDebugString("Effect was placed in software.");
break;
case DSFXR_UNALLOCATED:
OutputDebugString("Effect is not yet allocated to hardware or software.");
break;
}
}
return hr;
}
为了获取或者设置特技的参数,首先你要从包含特技的缓冲区对象中获取相应的特技接口指针。下面列出DirectSound支持的特技接口。
- IDirectSoundFXChorus8
- IDirectSoundFXCompressor8
- IDirectSoundFXDistortion8
- IDirectSoundFXEcho8
- IDirectSoundFXFlanger8
- IDirectSoundFXGargle8
- IDirectSoundFXI3DL2Reverb8
- IDirectSoundFXParamEq8
- IDirectSoundFXWavesReverb8
下面我简单介绍一下标准的特技
Chorus,
Compression
Distortion
Echo
Environmental Reverberation
Flange
Gargle
Parametric Equalizer
Waves Reverberation
3.6录制Capturing Waveforms
1枚举录音的设备
如果你的程序只是想从用户缺省的设备上进行声音的录制,那么就没有必要来枚举出系统中的所有录音的设备,当你调用DirectSoundCaptureCreate8 或者另外一个函数
DirectSoundFullDuplexCreate8的时候,其实就默认指定了一个缺省的录音设备。
当然,在下面的情况下,你就必须要枚举系统中所有的设备,
DWORD pv; // Can be any 32-bit type.
HRESULT hr = DirectSoundCaptureEnumerate(
(LPDSENUMCALLBACK)DSEnumProc, (VOID*)&pv);
2创建设备对象
你可以通过DirectSoundCaptureCreate8或者DirectSoundFullDuplexCreate8函数直接创建设备对象,该函数返回一个指向IDirectSoundCapture8接口的指针
3录音设备的性能
你可以通过IDirectSoundCapture8::GetCaps方法来获取录音的性能,这个函数的参数是一个DSCCAPS类型的结构,在传递这个参数之前,一定要初始化该结构的dwSize成员变量。同时,你可以通过这个结构返回设备支持的声道数,以及类似WAVEINCAPS结构的其他设备属性
4创建录音的buffer
我们可以通过IDirectSoundCapture8::CreateCaptureBuffer来创建一个录音的buffer对象,这个函数的一个参数采用DSCBUFFERDESC类型的结构来说明buffer的一些特性,这个结构的最后一个成员变量是一个WAVEFORMATEX结构,这个结构一定要初始化成泥需要的wav格式。
说明一下,如果你的应用程序一边播放的同时进行录制,如果你录制的buffer格式和你的主缓冲buffer不一样,那么你创建录制buffer对象就会失败,原因在于,有些声卡只支持一种时钟,不能同时支持录音和播放同时以两种不同的格式进行。
下面的函数,演示了如何创建一个录音的buffer对象,这个buffer对象能够处理1秒的数据,注意,这里传递的录音设备对象参数一定要通过DirectSoundCaptureCreate8来创建,而不是早期的DirectSoundCaptureCreate接口,否则,buffer对象不支持IDirectSoundCaptureBuffer8接口,
HRESULT CreateCaptureBuffer(LPDIRECTSOUNDCAPTURE8 pDSC,
LPDIRECTSOUNDCAPTUREBUFFER8* ppDSCB8)
{
HRESULT hr;
DSCBUFFERDESC dscbd;
LPDIRECTSOUNDCAPTUREBUFFER pDSCB;
WAVEFORMATEX wfx =
{WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0};
// wFormatTag, nChannels, nSamplesPerSec, mAvgBytesPerSec,
// nBlockAlign, wBitsPerSample, cbSize
if ((NULL == pDSC) || (NULL == ppDSCB8)) return E_INVALIDARG;
dscbd.dwSize = sizeof(DSCBUFFERDESC);
dscbd.dwFlags = 0;
dscbd.dwBufferBytes = wfx.nAvgBytesPerSec;
dscbd.dwReserved = 0;
dscbd.lpwfxFormat = &wfx;
dscbd.dwFXCount = 0;
dscbd.lpDSCFXDesc = NULL;
if (SUCCEEDED(hr = pDSC->CreateCaptureBuffer(&dscbd, &pDSCB, NULL)))
{
hr = pDSCB->QueryInterface(IID_IDirectSoundCaptureBuffer8, (LPVOID*)ppDSCB8);
pDSCB->Release();
}
return hr;
}
5通过录音buffer对象获取信息
你可以通过IDirectSoundCaptureBuffer8::GetCaps方法来获取录音buffer的大小,但一定要记得初始化DSCBCAPS结构类型参数的dwSize成员变量。
为了获取buffer中数据的格式,你可以通过IDirectSoundCaptureBuffer8::GetFormat.方法来获取buffer中的数据格式,这个函数通过WAVEFORMATEX结构返回音频数据的信息,
如果我们想知道一个录音buffer目前的状态如何,可以通过IDirectSoundCaptureBuffer8::GetStatus来获取,这个函数通过一个DWORD类型的参数来表示该buffer是否正在录音,
IDirectSoundCaptureBuffer8::GetCurrentPosition方法可以获取buffer中read指针和录制指针的偏差。Read指针指向填充到该buffer中的数据的最末端,capture指针则指向复制到硬件的数据的末端,你read指针指向的前段数据都是安全数据,你都可以安全的复制。
6录音buffer对象通知机制
为了安全的定期的从录音buffer中copy数据,你的应用程序就要知道,什么时候read指针指向了特定的位置,一个方法是通过IDirectSoundCaptureBuffer8::GetCurrentPosition.方法来获取read指针的位置,另外一个更有效的方法采用通知机制,通过IDirectSoundNotify8::SetNotificationPositions方法,你可以设置任何一个小于buffer的位置
来触发一个事件,切记,当buffer正在running的时候,不要设置。
如何来设置一个触发事件呢,首先要得到IDirectSoundNotify8接口指针,你可以通过buffer对象的QuerInterface来获取这个指针接口,对于你指定的任何一个position,你都要通过CreateEvent方法,创建一个win32内核对象, 然后将内核对象的句柄赋给DSBPOSITIONNOTIFY结构的hEventNotify成员,通过该结构的dwOffset来设置需要通知的位置在buffer中的偏移量。
最后将这个结构或者结构数组,传递给SetNotificationPositions函数,下面的例子设置了三个通知,当position达到buffer的一半时会触发一个通知,当达到buffer的末端时,触发第二个通知,当录音停止时触发第三个通知消息。
HRESULT SetCaptureNotifications(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB)
{
#define cEvents 3
LPDIRECTSOUNDNOTIFY8 pDSNotify;
WAVEFORMATEX wfx;
HANDLE rghEvent[cEvents] = {0};
DSBPOSITIONNOTIFY rgdsbpn[cEvents];
HRESULT hr;
if (NULL == pDSCB) return E_INVALIDARG;
if (FAILED(hr = pDSCB->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&pDSNotify)))
{
return hr;
}
if (FAILED(hr = pDSCB->GetFormat(&wfx, sizeof(WAVEFORMATEX), NULL)))
{
return hr;
}
// Create events.
for (int i = 0; i < cEvents; ++i)
{
rghEvent[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == rghEvent[i])
{
hr = GetLastError();
return hr;
}
}
// Describe notifications.
rgdsbpn[0].dwOffset = (wfx.nAvgBytesPerSec/2) -1;
rgdsbpn[0].hEventNotify = rghEvent[0];
rgdsbpn[1].dwOffset = wfx.nAvgBytesPerSec - 1;
rgdsbpn[1].hEventNotify = rghEvent[1];
rgdsbpn[2].dwOffset = DSBPN_OFFSETSTOP;
rgdsbpn[2].hEventNotify = rghEvent[2];
// Create notifications.
hr = pDSNotify->SetNotificationPositions(cEvents, rgdsbpn);
pDSNotify->Release();
return hr;
}
7录音的步骤
录音主要包括下面几个步骤
1调用IDirectSoundCaptureBuffer8::Start使buffer对象开始工作,通过你要给这个函数dwFlags传递一个DSCBSTART_LOOPING参数,注意,buffer就会不停工作,而不是当buffer填充满了就停止工作,当缓冲区满了后,就会从头重新填充
2等待你期望的事件通知,当buffer被填充到某个你期望的位置时,会触发通知。
3当你收到通知的时,你就要调用IDirectSoundCaptureBuffer8::Lock来锁住bufer的一部份,切记,不要将capture指针指向的内存锁住,你可以调用IDirectSoundCaptureBuffer8::GetCurrentPosition方法来获取read指针的位置。在传递给Lock函数的参数中,你一定要指定内存的大小和偏移量,这个函数会返回你锁住的内存的起始地址,以及block的大小,
4从你锁住的内存中复制data,
5复制完成后要记得IDirectSoundCaptureBuffer8::Unlock方法来解锁内存
6在你停止录音之前,你可以反复的重复2~5步骤,如果你想停止录音了,你可以调用
IDirectSoundCaptureBuffer8::Stop方法。
9将录音写入wav文件
WAV文件是采用RIFF格式的文件,在文件中包含一系列的chunks,来描述头信息和数据信息,win32API提供一套mmio系列函数用来操作RIFF格式的文件,但是Directsound并没有提供读写wav格式文件的函数,但是,Directsound里封装了一个CWaveFile类用来操作wav文件,可以通过open来写入文件的头信息,write来写入文件的数据,close函数写入文件的长度,关闭文件。
下面是代码,如何创建一个wav格式的文件
CWaveFile g_pWaveFile;
WAVEFORMATEX wfxInput;
ZeroMemory( &wfxInput, sizeof(wfxInput));
wfxInput.wFormatTag = WAVE_FORMAT_PCM;
wfxInput.nSamplesPerSec = 22050
wfxInput.wBitsPerSample = 8;
wfxInput.nChannels = 1;
wfxInput.nBlockAlign =
wfxInput.nChannels * (wfxInput.wBitsPerSample / 8);
wfxInput.nAvgBytesPerSec =
wfxInput.nBlockAlign * wfxInput.nSamplesPerSec;
g_pWaveFile = new CWaveFile;
if (FAILED(g_pWaveFile->Open("mywave.wav", &wfxInput,
WAVEFILE_WRITE)))
{
g_pWaveFile->Close();
}
下面的代码就演示了如何从buffer对象中将数据写入文件中
HRESULT RecordCapturedData()
{
HRESULT hr;
VOID* pbCaptureData = NULL;
DWORD dwCaptureLength;
VOID* pbCaptureData2 = NULL;
DWORD dwCaptureLength2;
VOID* pbPlayData = NULL;
UINT dwDataWrote;
DWORD dwReadPos;
LONG lLockSize;
if (NULL == g_pDSBCapture)
return S_FALSE;
if (NULL == g_pWaveFile)
return S_FALSE;
if (FAILED (hr = g_pDSBCapture->GetCurrentPosition(
NULL, &dwReadPos)))
return hr;
// Lock everything between the private cursor
// and the read cursor, allowing for wraparound.
lLockSize = dwReadPos - g_dwNextCaptureOffset;
if( lLockSize < 0 ) lLockSize += g_dwCaptureBufferSize;
if( lLockSize == 0 ) return S_FALSE;
if (FAILED(hr = g_pDSBCapture->Lock(
g_dwNextCaptureOffset, lLockSize,
&pbCaptureData, &dwCaptureLength,
&pbCaptureData2, &dwCaptureLength2, 0L)))
return hr;
// Write the data. This is done in two steps
// to account for wraparound.
if (FAILED( hr = g_pWaveFile->Write( dwCaptureLength,
(BYTE*)pbCaptureData, &dwDataWrote)))
return hr;
if (pbCaptureData2 != NULL)
{
if (FAILED(hr = g_pWaveFile->Write(
dwCaptureLength2, (BYTE*)pbCaptureData2,
&dwDataWrote)))
return hr;
}
// Unlock the capture buffer.
g_pDSBCapture->Unlock( pbCaptureData, dwCaptureLength,
pbCaptureData2, dwCaptureLength2 );
// Move the capture offset forward.
g_dwNextCaptureOffset += dwCaptureLength;
g_dwNextCaptureOffset %= g_dwCaptureBufferSize;
g_dwNextCaptureOffset += dwCaptureLength2;
g_dwNextCaptureOffset %= g_dwCaptureBufferSize;
return S_OK;
}