简体   繁体   English

Win32 PlaySound:如何控制音量?

[英]Win32 PlaySound: How to control the volume?

I'm using the Win32 MultiMedia function PlaySound to play a sound from my application.我正在使用 Win32 MultiMedia 函数PlaySound从我的应用程序中播放声音。

I would like to be able to dynamically adjust the volume of the sound that is being played without modifying the system volume level.我希望能够在修改系统音量级别的情况下动态调整正在播放的声音的音量。

The only suggestions I could find for manipulating the volume of sounds played via PlaySound was to use waveOutSetVolume , however that function sets the system-wide volume level (not what I want).我能找到的关于通过PlaySound播放声音音量的唯​​一建议是使用waveOutSetVolume ,但是该函数设置系统范围的音量级别(不是我想要的)。

Two possible solutions:两种可能的解决方案:

First, if you are targeting Vista and up, you can use the new Windows Audio APIs to adjust the per-application volume.首先,如果您的目标是 Vista 及更高版本,您可以使用新的 Windows 音频 API 来调整每个应用程序的音量。 ISimpleAudioVolume, IAudioEndpointVolume, etc... ISimpleAudioVolume、IAudioEndpointVolume 等...

If that's not suitable, can load the WAV file directly into memory and modify the samples in place.如果这不合适,可以将 WAV 文件直接加载到内存中并就地修改样本。 Try this:尝试这个:

Read the WAV file from disk and into a memory buffer and scale the samples back.将 WAV 文件从磁盘读取到内存缓冲区,然后按比例缩小样本。 I'm going to assume that the WAV file in question is 16-bit stereo with uncompressed (PCM) samples.我将假设有问题的 WAV 文件是带有未压缩 (PCM) 样本的 16 位立体声。 Stereo or mono.立体声或单声道。 If it's not, much of this goes out the window.如果不是,那么大部分内容都会消失。

I'll leave the reading of the WAV file bytes into memory as an exercise for the reader: But let's start with the following code, where "ReadWavFileIntoMemory" is your own function.我将把 WAV 文件字节读取到内存中作为练习留给读者:但是让我们从以下代码开始,其中“ReadWavFileIntoMemory”是您自己的函数。

DWORD dwFileSize;
BYTE* pFileBytes;
ReadWavFileIntoMemory(szFilename, &pFileBytes, &dwFileSize);

At this point, an inspection of pFileBytes will look something like the following:此时,对 pFileBytes 的检查将如下所示:

RIFF....WAVEfmt ............data....

This is the WAV file header.这是 WAV 文件头。 "data" is the start of the audio sample chunk. “数据”是音频样本块的开始。

Seek to the "data" portion, and read the 4 bytes following "data" into a DWORD.查找“数据”部分,并将“数据”后面的 4 个字节读入 DWORD。 This is the size of the "data" chunk that contains the audio samples.这是包含音频样本的“数据”块的大小。 The number of samples (assuming PCM 16-bit is this number divided by 2).样本数(假设 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;

Now, we'll create a sample pointer that points to the first real sample in our memory buffer:现在,我们将创建一个指向内存缓冲区中第一个真实样本的样本指针:

SHORT* pSample = (SHORT*)(pDataOffset + 8);

pSample points to the first 16-bit sample in the WAV file. pSample 指向 WAV 文件中的第一个 16 位样本。 As such, we're ready to scale the audio samples to the appropriate volume level.因此,我们已准备好将音频样本缩放到适当的音量级别。 Let's assume that our volume range is between 0.0 and 1.0.假设我们的音量范围在 0.0 和 1.0 之间。 Where 0.0 is complete silence.其中 0.0 是完全静音。 And 1.0 is the normal full volume.而1.0是正常的全音量。 Now we just multiply each sample by the target volume:现在我们只需将每个样本乘以目标体积:

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;
}

At this point, you are ready to play your in memory WAV file with PlaySound:此时,您已准备好使用 PlaySound 播放内存中的 WAV 文件:

PlaySound((LPCSTR)pFileBytes, NULL, SND_MEMORY);

And that should do it.那应该这样做。 If you are going to use the SND_ASYNC flag to make the above call non-blocking, then you won't be able to free your memory buffer until it has finished playing.如果您打算使用 SND_ASYNC 标志使上述调用成为非阻塞的,那么您将无法释放内存缓冲区,直到它完成播放。 So be careful.所以要小心。

As for the parsing of the WAV file header.至于WAV文件头的解析。 I hand-waved my way out of that by declaring a hypothetical function called "FindDataChunk".我通过声明一个名为“FindDataChunk”的假设函数来摆脱这种情况。 You should probably invest in writing a proper WAV file header parser rather than just seeking to where you first encounter "data" in the header.您可能应该投资于编写适当的 WAV 文件头解析器,而不是仅仅寻找您第一次在头中遇到“数据”的位置。 For the sake of brevity, I left out the usual error checking.为了简洁起见,我省略了通常的错误检查。 As such, there may be a few security concerns to address with the above code - especially as it relates to traversing the memory buffer and writing into it.因此,上面的代码可能需要解决一些安全问题——尤其是涉及到遍历内存缓冲区并写入其中时。

Adding as an answer to implementation of @selbie clean solution.添加作为@selbie clean 解决方案实施的答案。

#include <Windows.h>
#include <string>
#include <iostream>
#include <fstream>
#include <conio.h>

using namespace std;

void ReadWavFileIntoMemory(string fname, BYTE** pb, DWORD *fsize){
    ifstream f(fname, ios::binary);

    f.seekg(0, ios::end);
    int lim = f.tellg();
    *fsize = lim;

    *pb = new BYTE[lim];
    f.seekg(0, ios::beg);

    f.read((char *)*pb, lim);

    f.close();
}

int main(){
    DWORD dwFileSize;
    BYTE* pFileBytes;
    ReadWavFileIntoMemory("D:\\OpenAL 1.1 SDK\\samples\\media\\fiveptone.wav", &pFileBytes, &dwFileSize);

    BYTE* pDataOffset = (pFileBytes + 40);

    cout << "Length: " << dwFileSize << endl;

    float fVolume = 0.02;

    __int16 * p = (__int16 *)(pDataOffset + 8);
    cout << sizeof(*p) << endl;
    for (int i = 80 / sizeof(*p); i < dwFileSize / sizeof(*p); i++){
        p[i] = (float)p[i] * fVolume;
    }

    cout << "PlaySound" << endl;
    PlaySound((LPCSTR)pFileBytes, NULL, SND_MEMORY);

    return 0;
}

The problem I faced with the proper sized datatype (here I used __int16) for manipulation in wav file.我在使用适当大小的数据类型(这里我使用 __int16)在 wav 文件中进行操作时遇到的问题。

The wav file I used comes with OpenAL media files "fiveptone.wav".我使用的 wav 文件带有 OpenAL 媒体文件“fiveptone.wav”。

Please feel free to modify because the offset I searched from documentations of wav files and some trial and error.请随意修改,因为我从 wav 文件的文档中搜索的偏移量以及一些试验和错误。

Another example (this one in Rust)另一个例子(这个在 Rust 中)

#![allow(uncommon_codepoints)]
#![feature(const_float_bits_conv)]

winapi = {version = "0.3", features = ["mmeapi", "playsoundapi"]}

use core::convert::TryInto;
use core::ptr::{addr_of, read_unaligned, null_mut};
use gstuff::re::Re;
use std::path::Path;
use winapi::shared::mmreg::WAVEFORMATEX;
/// synchronously play a `wav` file at a `vol`ume
fn play_vol (wav: &dyn AsRef<Path>, vol: f32) -> Re<()> {
  let mut wavᵇ = gstuff::slurp (wav);

  // http://soundfile.sapp.org/doc/WaveFormat/
  // https://docs.rs/winapi/latest/winapi/shared/mmreg/struct.WAVEFORMATEX.html
  let header: *const WAVEFORMATEX = &wavᵇ[20] as *const u8 as *const WAVEFORMATEX;
  let bits_per_sample = unsafe {read_unaligned (addr_of! ((*header).wBitsPerSample))};
  if bits_per_sample != 32 {fail! ([=bits_per_sample])}
  if &wavᵇ[36..40] != b"data" {fail! ("unexpected Subchunk2ID")}
  let subchunk2size = u32::from_le_bytes ((&wavᵇ[40..44]) .try_into()?) as usize;
  if subchunk2size != wavᵇ.len() - 44 {fail! ([=subchunk2size] " vs " [=wavᵇ.len()])}
  let samples = subchunk2size / 4;

  for sx in 0 .. samples {
    let sampleᵇ = &mut wavᵇ[44 + sx * 4 .. 44 + sx * 4 + 4];
    let mut sample = f32::from_le_bytes (sampleᵇ.try_into()?);
    sample *= vol;
    let sb: [u8; 4] = sample.to_le_bytes();
    sampleᵇ[0] = sb[0]; sampleᵇ[1] = sb[1]; sampleᵇ[2] = sb[2]; sampleᵇ[3] = sb[3]}

  // https://docs.microsoft.com/en-us/previous-versions/dd743680(v=vs.85)
  let flags = winapi::um::playsoundapi::SND_MEMORY;
  unsafe {winapi::um::playsoundapi::PlaySoundA (
    wavᵇ.as_ptr() as *const i8,
    null_mut(),
    flags)};
  Re::Ok(())}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM