部分XAudio2的方法中含有OperationSet参数,这个参数能决定是否延迟执行这些方法(仅添加一个挂起的操作)。然后在某个特定的时间通过调用XAudio2::CommitChanges,并将它的OperationSet参数指定为目标操作的ID,这样就能让XAudio2组件真正执行对应的操作。这个ID没有特殊要求,只要互不相同即可,因此可以用一个全局计数器来作为它们的ID,每次使用后增加计数即可。这样一来,不同代码就可以互不冲突地提交修改(全局计数器同时也可用于区分提交修改的时间先后)。

同时,以原子方式提交的多个操作被确保是以采样率匹配方式混音的(一个混合音轨方面的术语,防止混音时因采样率的不准确而导致播放时长的变化,详见这里)。比如,声音的播放将会是同步的。另一方面,如果用XAUDIO2_COMMIT_NOW(值为0)作为OperationSet参数的值,改动将立即生效。如果以XAUDIO2_COMMIT_ALL(值也为0)参数调用CommitChanges,则所有挂起的操作都将生效,这时OperationSet的ID将被忽略。

例如,分别调用

pSourceVoice1->Start(0, 1);
pSourceVoice2->Start(0, 2);

// 这时声音还未播放

pXaudio2->CommitChanges(XAUDIO2_COMMIT_ALL);    // 声音1、2同时播放
//pXaudio2->CommitChanges(1);                   // 仅播放声音1
//pXaudio2->CommitChanges(2);                   // 仅播放声音2

pSourceVoice3->Start();                         // 声音3将立即播放(Start方法的两个参数默认值均为0)

在XAudio2的回调函数中用XAUDIO2_COMMIT_NOW调用一些方法时,操作会立即生效,而其他用XAUDIO2_COMMIT_NOW调用的方法将在方法返回后的下一个处理时机才会生效,或者等到用相同的OperationSet值调用CommitCahnges时生效。因此调用的生效顺序并不总是与调用顺序相等。

所有挂起的操作在IXAudio2::StopEngine被调用时将以原子方式提交。任何在引擎停止时调用的方法都会无视OperationSet参数而立即生效(以同步模式)。当你重新启动引擎时,XAudio2会恢复异步模式。

// 但我在测试时发现在调用StopEngine后
// 以挂起方式调用的方法并没有立即生效,不知是不是我的理解有错,代码如下
hr = pSourceVoice->Start(0, 0);
Sleep(4000);
pXaudio2->StopEngine();
wprintf(L"Engine Stopped!\n");
Sleep(4000);
hr = pSourceVoice->SetVolume(0.1f, 1);
//hr = pSourceVoice->SetVolume(0.1f, 0);  // 如果以XAUDIO2_COMMIT_NOW调用则重启引擎时已经生效
Sleep(4000);
pXaudio2->StartEngine();
wprintf(L"Engine Restart\n");
Sleep(4000);
pXaudio2->CommitChanges(1);               // 修改音量的操作直到此时才生效,而不是在重启引擎时已经生效

以下为操作集的几个简单场景下的有效应用:
1、同时播放多个声音。
2、同时提交声音Buffer(SubmitSourceBuffer),设置声音参数,播放声音。
3、对音频进行大规模的修改参数,如把所有source voices连接到一个新的submix voice上。

更多:XAudio2 Operation Set