函数waveOutSetVolume可以用来调整系统的音量,其定义如下:
MMRESULT WINAPI waveOutSetVolume(HWAVEOUT hwo, DWORD dwVolume); |
其中,第一个参数HWAVEOUT hwo可以传入两种值,第一种是wave-out设备ID号,此时函数调整的是设备音量,第二种是Stream句柄,调整的是所指向的Stream的音量。
今天为这个地方晕乎乎了半天,最后还是写了点测试程序才搞懂,有关这个函数的具体使用方法及操作细节请看下面的验证过程。
注:
下面的代码实际上是一个应用程序中部分代码,有关该应用程序的源码可以从我的资源中下载,主界面如下:
下面的代码实现了对设备音量进行调整的功能,对应上图中的Loud和Soft按键的功能。其中函数void CWaveAPI_TestDlg::OnBnClickedButton1()实现了将设备音量(0~0xffff ffff)增加0x1000的功能,也即Loud按钮的功能,而按钮Soft的功能由函数void CWaveAPI_TestDlg::OnBnClickedButton2()实现,每次点击该按钮设备音量将减少0x1000。
详细如下:
static DWORD g_dwWaveOutGain = 0;
void CWaveAPI_TestDlg::OnBnClickedButton1() { // TODO: Add your control notification handler code here DWORD dwDeviceID = 0; DWORD deDeviceGain = 0;
for (dwDeviceID = 0; dwDeviceID < waveOutGetNumDevs(); dwDeviceID++) {
NKDbgPrintfW(L"Device #%d/r/n", dwDeviceID);
waveOutSetVolume((HWAVEOUT)dwDeviceID, g_dwWaveOutGain); waveOutGetVolume((HWAVEOUT)dwDeviceID, &deDeviceGain);
NKDbgPrintfW(L"/r/n dst 0x%8x, read 0x%8x/r/n", g_dwWaveOutGain, deDeviceGain);
if (g_dwWaveOutGain <= (0xffffffff - 0x1000)) g_dwWaveOutGain+=0x1000; else g_dwWaveOutGain = 0xffffffff; } }
void CWaveAPI_TestDlg::OnBnClickedButton2() { // TODO: Add your control notification handler code here DWORD dwDeviceID = 0; DWORD deDeviceGain = 0;
for (dwDeviceID = 0; dwDeviceID < waveOutGetNumDevs(); dwDeviceID++) {
NKDbgPrintfW(L"Device #%d/r/n", dwDeviceID);
waveOutSetVolume((HWAVEOUT)dwDeviceID, g_dwWaveOutGain); waveOutGetVolume((HWAVEOUT)dwDeviceID, &deDeviceGain);
NKDbgPrintfW(L"/r/n dst 0x%8x, read 0x%8x/r/n", g_dwWaveOutGain, deDeviceGain);
if (g_dwWaveOutGain >= 0x1000) g_dwWaveOutGain-=0x1000; else g_dwWaveOutGain = 0; } } |
下面的代码中包括下面三个函数:
int StringFormatToWaveFormatEx( WAVEFORMATEX *wfx, const TCHAR* szWaveFormat ); ULONG SineWave( void* pBuffer, ULONG ulNumBytes, WAVEFORMATEX *pwfx, double dFrequency ); void PlayBackSound(bool bLoud); |
其中第一个函数StringFormatToWaveFormatEx实现了格式转换的功能。我们一般都习惯上使用形如“WAVE_FORMAT_1M08”的标记来表示wave in/out的格式,该函数完成在形如“WAVE_FORMAT_1M08”的标志和结构体WAVEFORMATEX之间进行等价转换的功能。
/* defines for dwFormat field of WAVEINCAPS and WAVEOUTCAPS */ #define WAVE_INVALIDFORMAT 0x00000000 /* invalid format */ #define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ #define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ #define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ #define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ #define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ #define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ #define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ #define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ #define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ |
typedef struct { WORD wFormatTag; // format type WORD nChannels; // number of channels (i.e. mono, stereo...) DWORD nSamplesPerSec; // sample rate DWORD nAvgBytesPerSec; // for buffer estimation WORD nBlockAlign; // block size of data WORD wBitsPerSample; // number of bits per sample of mono data WORD cbSize; // the count in bytes of the size of // extra information (after cbSize) } WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX; |
接下来说第二个函数SineWave,它完成根据调用者传入的参数构建正选波数据到pBuffer的功能,可以省却我们解析wav文件的烦恼。
第三个函数PlayBackSound是今天的主角,它完成了创建Stream,并使用Stream播放音频的同时调整音量的功能。
详细的代码如下:
// 转换数据 int StringFormatToWaveFormatEx( WAVEFORMATEX *wfx, const TCHAR* szWaveFormat ) { int iRet; DWORD tr=true; TCHAR channels,bits1,bits2;
ZeroMemory(wfx,sizeof(*wfx));
iRet = _stscanf ( szWaveFormat, TEXT("WAVE_FORMAT_%i%c%c%c"), &wfx->nSamplesPerSec, &channels, &bits1, &bits2 ); if( iRet != 4 ) { NKDbgPrintfW( ( TEXT( "ERROR: %s not recognized/n" ), (LPWSTR)szWaveFormat ) ); NKDbgPrintfW(TEXT("Possible Cause: Supplied format not in the form of WAVE_FORMAT%%i%%c%%c%%c/n")); tr = false; goto Error; }
wfx->wFormatTag = WAVE_FORMAT_PCM; switch( channels ) { case 'M': case 'm': wfx->nChannels=1; break;
default: wfx->nChannels=2; }
switch( wfx->nSamplesPerSec ) { case 1: case 2: case 4: wfx->nSamplesPerSec=11025*wfx->nSamplesPerSec; }
wfx->wBitsPerSample=(bits1-TEXT('0'))*10+(bits2-TEXT('0')); wfx->nBlockAlign=wfx->nChannels*wfx->wBitsPerSample/8; wfx->nAvgBytesPerSec=wfx->nSamplesPerSec*wfx->nBlockAlign;
Error: return tr; } |
// 产生正选波数据 ULONG SineWave( void* pBuffer, ULONG ulNumBytes, WAVEFORMATEX *pwfx, double dFrequency ) { double dPhase = 0.0; double dAmplitude = 1.0; char *pClear, *pClearEnd; const double TWOPI = 2* 3.1415926535897931;
pClearEnd = (char*)pBuffer + ulNumBytes; pClear = pClearEnd - ulNumBytes % 4; while( pClear<pClearEnd ) { *pClear = 0; pClear++; }
int nsamples = ulNumBytes / pwfx->nBlockAlign; int i, m_t0 = 0; int m_dSampleRate = pwfx->nSamplesPerSec; double dMultiplier, dOffset, dSample;
if( pwfx->wBitsPerSample == 8 ) { unsigned char * pSamples = (unsigned char *) pBuffer; dMultiplier = 127.0; dOffset = 128.0; if( pwfx->nChannels == 1 ) { for (i = 0; i < nsamples; i ++ ) { double t = ((double) (i+m_t0)) / m_dSampleRate; dSample = dAmplitude * sin( t*dFrequency* TWOPI + dPhase ); pSamples[i] = (unsigned char) (dSample * dMultiplier + dOffset); } } else { for (i = 0; i < nsamples; i ++) { double t = ((double) (i+m_t0)) / m_dSampleRate; dSample = dAmplitude * sin (t*dFrequency* TWOPI + dPhase); pSamples[2*i] = (unsigned char) (dSample * dMultiplier + dOffset); pSamples[2*i+1] = pSamples[2*i]; // replicate across both channels } } } else { short * pSamples = (short *) pBuffer; const double dMultiplier2 = 32767.0; const double dOffset2 = 0.0; if( pwfx->nChannels == 1 ) { for (i = 0; i < nsamples; i += 1) { double t = ((double) (i+m_t0)) / m_dSampleRate; dSample = dAmplitude * sin (t*dFrequency* TWOPI + dPhase); pSamples[i] = (short) (dSample * dMultiplier2 + dOffset2); } } else { for (i = 0; i < nsamples; i ++) { double t = ((double) (i+m_t0)) / m_dSampleRate; dSample = dAmplitude * sin( t*dFrequency* TWOPI + dPhase ); pSamples[2*i] = (short) (dSample * dMultiplier2 + dOffset2); pSamples[2*i+1] = pSamples[2*i]; // replicate across both channels } } } m_t0 += i; return ulNumBytes; } |
/* 功能: 播放正选波声音文件 参数: bLoud:调整播放声音增益 true: 增大 false: 减小 返回值: 无 */ void PlayBackSound(bool bLoud) { // TODO: Add your control notification handler code here HWAVEOUT hwo = NULL; WAVEFORMATEX wfx; MMRESULT hr; static HANDLE hEvent; WAVEHDR wh; char *data = NULL; DWORD dwSeconds = 1; double dFrequency = 440.0; DWORD dwExpectedPlaytime, dwTime = 0; DWORD dwSleepInterval = 50; //50 milliseconds
// *********************************************************************** // 1. 初始化相关资源 // *********************************************************************** // 初始化WAVEFORMATEX变量wfx if (!StringFormatToWaveFormatEx(&wfx,TEXT("WAVE_FORMAT_2M16"))) { NKDbgPrintfW(L"Convert Audio Format Failed!/r/n"); return ; }
// 输出格式信息 NKDbgPrintfW(L"************************************/r/n"); NKDbgPrintfW(L"wfx.wBitsPerSample %d/r/n", wfx.wBitsPerSample); NKDbgPrintfW(L"wfx.nChannels %d/r/n", wfx.nChannels); NKDbgPrintfW(L"wfx.nSamplesPerSec %d/r/n", wfx.nSamplesPerSec); NKDbgPrintfW(L"************************************/r/n");
hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
// 打开wave out设备 hr = waveOutOpen(&hwo, 0, &wfx, NULL,(DWORD)hEvent, CALLBACK_NULL); if(hr != MMSYSERR_NOERROR) { NKDbgPrintfW(L"waveOutOpen failed! Error number 0x%x/r/n", hr); return ; }
// 分配buffer data = new char[dwSeconds * wfx.nAvgBytesPerSec]; if (!data) { NKDbgPrintfW(L"Out of memeory/r/n"); return ; }
ZeroMemory(data, dwSeconds * wfx.nAvgBytesPerSec);
// 初始化wh(WAVEHDR) ZeroMemory(&wh, sizeof(WAVEHDR)); wh.lpData = data; wh.dwBufferLength = dwSeconds * wfx.nAvgBytesPerSec; wh.dwLoops = 1; wh.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP; dwExpectedPlaytime = wh.dwBufferLength * 1000 / wfx.nAvgBytesPerSec;
// 创造声波 SineWave(wh.lpData, wh.dwBufferLength, &wfx,dFrequency );
// *********************************************************************** // 2. 开始播放声音操作 // *********************************************************************** // 调整Stream音量 DWORD deDeviceGain = 0;
waveOutSetVolume((HWAVEOUT)hwo, g_dwWaveOutGain); waveOutGetVolume((HWAVEOUT)hwo, &deDeviceGain);
NKDbgPrintfW(L"Volume of device 0 successfully set to 0x%8x, previous 0x%8x/r/n", g_dwWaveOutGain, deDeviceGain);
if (bLoud) { if (g_dwWaveOutGain <= (0xffffffff - 0x1000)) g_dwWaveOutGain+=0x1000; else g_dwWaveOutGain = 0xffffffff; }else { if (g_dwWaveOutGain >= 0x1000) g_dwWaveOutGain-=0x1000; else g_dwWaveOutGain = 0; }
// 准备buffer hr = waveOutPrepareHeader(hwo, &wh, sizeof(WAVEHDR)); if(hr != MMSYSERR_NOERROR) { NKDbgPrintfW(L"waveOutPrepareHeader failed! Error number 0x%x/r/n", hr); return ; }
// 写入buffer hr = waveOutWrite(hwo, &wh, sizeof(WAVEHDR));
while( (!(wh.dwFlags&WHDR_DONE)) && ( dwTime<dwExpectedPlaytime+1000) ) { Sleep( dwSleepInterval ); dwTime += dwSleepInterval; }
// *********************************************************************** // 3. 销毁所有资源 // *********************************************************************** NKDbgPrintfW(L"Destroy all the resource/r/n"); waveOutReset(hwo); if(hr != MMSYSERR_NOERROR) { NKDbgPrintfW(L"waveOutUnprepareHeader failed! Error number 0x%x/r/n", hr); return ; }
hr = waveOutUnprepareHeader(hwo,&wh,sizeof(WAVEHDR) ); if(hr != MMSYSERR_NOERROR) { NKDbgPrintfW(L"waveOutUnprepareHeader failed! Error number 0x%x/r/n", hr); return ; }
waveOutClose(hwo); if(hr != MMSYSERR_NOERROR) { NKDbgPrintfW(L"waveOutUnprepareHeader failed! Error number 0x%x/r/n", hr); return ; }
if (data) delete []data;
if( hEvent) CloseHandle(hEvent); } |
前面提到,应用程序中有四个按钮,左边的两个按钮已经讲过,右边的两个按钮“Loud Stream”和“Soft Stream”的实现就是通过调用上面的函数PlayBackSound完成,这部分代码如下:
void CWaveAPI_TestDlg::OnBnClickedButton3() { PlayBackSound(true); }
void CWaveAPI_TestDlg::OnBnClickedButton4() { // TODO: Add your control notification handler code here PlayBackSound(false); } |