Win32 PlaySound:如何控制音量?

Dav*_*ron 2 c++ audio winapi multimedia

我正在使用Win32 MultiMedia函数PlaySound从我的应用程序播放声音.

我希望能够在修改系统音量的情况下动态调整正在播放的声音音量.

我可以找到的唯一建议来操作通过PlaySound播放的声音音量是使用waveOutSetVolume,但是该功能设置系统范围的音量级别(不是我想要的).

sel*_*bie 11

两种可能的解决方

首先,如果您的目标是Vista及更高版本,则可以使用新的Windows Audio API来调整每个应用程序的数量.ISimpleAudioVolume,IAudioEndpointVolume等......

如果这不合适,可以将WAV文件直接加载到内存中并修改样本.试试这个:

从磁盘读取WAV文件并进入内存缓冲区并缩回样本.我将假设所讨论的WAV文件是带有未压缩(PCM)样本的16位立体声.立体声或单声道.如果不是这样的话,大部分都会消失.

我将把WAV文件字节的读取留给内存作为读者的练习:但是让我们从下面的代码开始,其中"ReadWavFileIntoMemory"是你自己的函数.

DWORD dwFileSize;
BYTE* pFileBytes;
ReadWavFileIntoMemory(szFilename, &pFileBytes, &dwFileSize);
Run Code Online (Sandbox Code Playgroud)

此时,对pFileBytes的检查将如下所示:

RIFF....WAVEfmt ............data....
Run Code Online (Sandbox Code Playgroud)

这是WAV文件头."data"是音频样本块的开头.

寻找"数据"部分,并将"数据"后的4个字节读入DWORD.这是包含音频样本的"数据"块的大小.采样数(假设PCM 16位是这个数除以2).

// FindDataChunk is your function that parses the WAV file and returns the pointer to the "data" chunk.
BYTE* pDataOffset = FindDataChunk(pBuffer);
DWORD dwNumSampleBytes = *(DWORD*)(pDataOffset + 4);
DWORD dwNumSamples = dwNumSamplesBytes / 2;
Run Code Online (Sandbox Code Playgroud)

现在,我们将创建一个指向内存缓冲区中第一个真实样本的示例指针:

SHORT* pSample = (SHORT*)(pDataOffset + 8);
Run Code Online (Sandbox Code Playgroud)

pSample指向WAV文件中的第一个16位样本.因此,我们已准备好将音频样本缩放到适当的音量级别.我们假设我们的音量范围介于0.0和1.0之间.其中0.0是完全沉默.1.0是正常的全音量.现在我们将每个样本乘以目标体积:

float fVolume = 0.5; // half-volume
for (DWORD dwIndex = 0; dwIndex < dwNumSamples; dwIndex++)
{
    SHORT shSample = *pSample;
    shSample = (SHORT)(shSample * fVolume);
    *pSample = shSample;
    pSample++;


    if (((BYTE*)pSample) >= (pFileBytes + dwFileSize - 1))
       break;
}
Run Code Online (Sandbox Code Playgroud)

此时,您已准备好使用PlaySound播放内存中的WAV文件:

PlaySound((LPCSTR)pFileBytes, NULL, SND_MEMORY);
Run Code Online (Sandbox Code Playgroud)

这应该做到这一点.如果您打算使用SND_ASYNC标志使上述调用无阻塞,那么在完成播放之前,您将无法释放内存缓冲区.所以要小心.

至于解析WAV文件头.我通过声明一个名为"FindDataChunk"的假设函数,向我挥手告别.您可能应该投资编写一个正确的WAV文件头解析器,而不是仅仅寻找您在头中首次遇到"数据"的位置.为简洁起见,我省略了通常的错误检查.因此,使用上述代码可能需要解决一些安全问题 - 特别是因为它涉及遍历内存缓冲区并写入内存缓冲区.