[英]Win32 PlaySound: How to control the volume?
我正在使用 Win32 MultiMedia 函數PlaySound從我的應用程序中播放聲音。
我希望能夠在不修改系統音量級別的情況下動態調整正在播放的聲音的音量。
我能找到的關於通過PlaySound播放聲音音量的唯一建議是使用waveOutSetVolume ,但是該函數設置系統范圍的音量級別(不是我想要的)。
兩種可能的解決方案:
首先,如果您的目標是 Vista 及更高版本,您可以使用新的 Windows 音頻 API 來調整每個應用程序的音量。 ISimpleAudioVolume、IAudioEndpointVolume 等...
如果這不合適,可以將 WAV 文件直接加載到內存中並就地修改樣本。 嘗試這個:
將 WAV 文件從磁盤讀取到內存緩沖區,然后按比例縮小樣本。 我將假設有問題的 WAV 文件是帶有未壓縮 (PCM) 樣本的 16 位立體聲。 立體聲或單聲道。 如果不是,那么大部分內容都會消失。
我將把 WAV 文件字節讀取到內存中作為練習留給讀者:但是讓我們從以下代碼開始,其中“ReadWavFileIntoMemory”是您自己的函數。
DWORD dwFileSize;
BYTE* pFileBytes;
ReadWavFileIntoMemory(szFilename, &pFileBytes, &dwFileSize);
此時,對 pFileBytes 的檢查將如下所示:
RIFF....WAVEfmt ............data....
這是 WAV 文件頭。 “數據”是音頻樣本塊的開始。
查找“數據”部分,並將“數據”后面的 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;
現在,我們將創建一個指向內存緩沖區中第一個真實樣本的樣本指針:
SHORT* pSample = (SHORT*)(pDataOffset + 8);
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;
}
此時,您已准備好使用 PlaySound 播放內存中的 WAV 文件:
PlaySound((LPCSTR)pFileBytes, NULL, SND_MEMORY);
那應該這樣做。 如果您打算使用 SND_ASYNC 標志使上述調用成為非阻塞的,那么您將無法釋放內存緩沖區,直到它完成播放。 所以要小心。
至於WAV文件頭的解析。 我通過聲明一個名為“FindDataChunk”的假設函數來擺脫這種情況。 您可能應該投資於編寫適當的 WAV 文件頭解析器,而不是僅僅尋找您第一次在頭中遇到“數據”的位置。 為了簡潔起見,我省略了通常的錯誤檢查。 因此,上面的代碼可能需要解決一些安全問題——尤其是涉及到遍歷內存緩沖區並寫入其中時。
添加作為@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;
}
我在使用適當大小的數據類型(這里我使用 __int16)在 wav 文件中進行操作時遇到的問題。
我使用的 wav 文件帶有 OpenAL 媒體文件“fiveptone.wav”。
請隨意修改,因為我從 wav 文件的文檔中搜索的偏移量以及一些試驗和錯誤。
另一個例子(這個在 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.