中文原文地址:https://msdn.microsoft.com/zh-cn/magazine/dn201755.aspx
英文原文地址:https://msdn.microsoft.com/en-us/magazine/dn201755.aspx?utm_source=tuicool&utm_medium=referral
DirectX 的 XAudio2 组件更多只是方式在 Windows 8 应用程序中播放声音和音乐。 我是来查看它而是作为一多功能工程组的声音处理。 通过使用多个 IXAudio2SourceVoice 和 IXAudio2SubmixVoice 的情况下,程序员可以将声音拆分成单独的管道进行自定义处理,然后将它们合并在最后 IXAudio2MasteringVoice 组合。
如在此列的在上一篇文章中 (msdn.microsoft.com/magazine/dn198248),XAudio2 允许标准音频将筛选器应用于源和 submix 的声音。 这些筛选器衰减的频率范围,并因此改变的谐波含量和音色的声音。
但更强大的是广义的设施,提供对通过声音传递实际的音频流的访问。 你可以简单地分析这个音频流,或者修改它。
这一设施被非正式地称为"音频效果"。更正式,它涉及到的音频处理对象 (APO),也称为一个跨平台的阿婆或 XAPO,它可以使用 Xbox 360 的应用程序,以及 Windows 时创建。
XAudio2 包括两个预定义的 APOs 常见任务。 XAudio2CreateVolumeMeter 函数创建允许一个程序来动态地获取应用方便的时间间隔在音频流的峰值振幅的 APO。 XAudio2CreateReverb 函数创建适用于基于 23 运行时参数语音的回声或混响的 APO — — 在此上下文中的"运行时"是指亚洲生产力组织积极处理音频时可以动态更改的参数。 此外,一个称为 XAPOFX 的库提供了回声和混响效果,以及卷力矩限制器和一个四波段均衡器。
APO 实现 IXAPO 接口,接口和 APO 与运行时参数实现 IXAPOParameters 接口。 但比较简单的方法创建您自己的 APOs 涉及衍生物ing 从的 CXAPOBase 和 CXAPOParametersBase 的类,这些实现这些接口和处理开销。
从这两个类派生是我会在这篇文章中使用的策略。 除了其他的头文件和重要图书馆我在先前的专栏中讨论过,项目的实施 APOs 需要引用 xapobase.h 头文件和 xapobase.lib 的导入库。
在讨论之前 APO 类的内部结构,让我向您展示如何将效果应用于 XAudio2 的声音。 在此列的可下载代码中的 SimpleEffectDemo 项目允许您从您的 Windows 8 的音乐库加载一个文件并播放它。 这是我已经在前面的列中显示的代码类似:文件加载和解码使用媒体基础类,以及 XAudio2 和玩。 SimpleEffectDemo 创建只有两个 XAudio2 声音:用于生成的音频和漏斗音频到声音硬件所需的掌握语音源声音。
SimpleEffectDemo 还包含两个非设定 CXAPO基地称为 OneSecondTremoloEffect (这适用颤音或摇摆不定的卷,基于简单的振幅调制) 和 OneSecondEchoEffect 的衍生品。 图 1 显示加载的音乐文件与运行的程序。这两个效果的每个启用或禁用的切换按钮。 屏幕截图显示启用的回声效果但颤音效果被禁用。
图 1 SimpleEffectDemo Program 与两个音频效果
APOs 可以应用于任何类型的 XAudio2 的声音。 当应用于源声音或 submix 声音,音频处理后发生内置过滤器您设置与 SetFilterParameters,但在应用到音频发送到使用 SetOutputFilterParameters 其他声音的筛选器之前。
我选择对掌握语音应用这些两个效果。 实例化影响并将它们附加到掌握声音的代码所示图 2。 每个效果引用与 XAUDIO2_EFFECT_DESCRIPTOR。 如果有多个效果 (正如在这里的情况),你使用这种结构的数组。 所结构 (或数组) 然后由一个 XAUDIO2_EFFECT_CHAIN 结构,传递给 SetEffectChain 方法支持所有 XAudio2 声音的引用。 该命令影响的事项:在这种情况下回声效果得到音频流已应用的颤音效果。
图 2 将两个音频效果应用到一个掌握的声音
// Create tremolo effect ComPtr<OneSecondTremoloEffect> pTremoloEffect = new OneSecondTremoloEffect(); // Create echo effect ComPtr<OneSecondEchoEffect> pEchoEffect = new OneSecondEchoEffect(); // Reference those effects with an effect descriptor array std::array<XAUDIO2_EFFECT_DESCRIPTOR, 2> effectDescriptors; effectDescriptors[0].pEffect = pTremoloEffect.Get(); effectDescriptors[0].InitialState = tremoloToggle->IsChecked->Value; effectDescriptors[0].OutputChannels = 2; effectDescriptors[1].pEffect = pEchoEffect.Get(); effectDescriptors[1].InitialState = echoToggle->IsChecked->Value; effectDescriptors[1].OutputChannels = 2; // Reference that array with an effect chain XAUDIO2_EFFECT_CHAIN effectChain; effectChain.EffectCount = effectDescriptors.size(); effectChain.pEffectDescriptors = effectDescriptors.data(); hresult = pMasteringVoice->SetEffectChain(&effectChain); if (FAILED(hresult)) throw ref new COMException(hresult, "pMasteringVoice->SetEffectChain failure");
在 SetEffectChain 调用后,效果实例不应进一步引用由该程序。 XAudio2 已添加到这些实例的引用和该程序可以释放自己的副本,或 ComPtr 可以为你做的。 从这里,影响确定的指数 — — 在本例中 0 的颤音效果和 1 的回声效果。 你可能想要使用这些常量的枚举。
为两者的影响,我已经将 XAUDIO2_ 的 InitialState 字段设置EFFECT_DESCRIPTOR 到切换按钮选中状态。此功能控制效果是最初启用还是禁用。 后来启用和禁用由选中和未选中的处理的两个切换按钮控件,如图所示的影响图 3。
图 3 启用和禁用音频效果
void MainPage::OnTremoloToggleChecked(Object^ sender, RoutedEventArgs^ args) { EnableDisableEffect(safe_cast<ToggleButton^>(sender), 0); } void MainPage::OnEchoToggleChecked(Object^ sender, RoutedEventArgs^ args) { EnableDisableEffect(safe_cast<ToggleButton^>(sender), 1); } void MainPage::EnableDisableEffect(ToggleButton^ toggle, int index) { HRESULT hresult = toggle->IsChecked->Value ? pMasteringVoice->EnableEffect(index) : pMasteringVoice->DisableEffect(index); if (FAILED(hresult)) throw ref new COMException(hresult, "pMasteringVoice->Enable/DisableEffect " + index.ToString()); }
OneSecondTremoloEffect 和 OneSecondEchoEffect 从 CXAPOBase 派生。 也许你会遇到从该类派生时的第一个困惑处理 CXAPOBase 构造函数。 此构造函数需要指向已初始化的 XAPO_REGISTRATION_PROPERTIES 结构的指针,但如何不会初始化此结构吗? C + + 要求在基类的构造函数完成之前执行任何派生类中的代码。
这是有点困惑,您可以通过定义和初始化结构作为全局变量或静态字段,或在一个静态方法的范围内解决。在这种情况下的静态字段的方法,你可以看到在 OneSecondTremoloEffect.h 头文件中到喜欢图 4。
图 4 的 OneSecondTremoloEffect.h 头文件
#pragma once class OneSecondTremoloEffect sealed : public CXAPOBase { private: static const XAPO_REGISTRATION_PROPERTIES RegistrationProps; WAVEFORMATEX waveFormat; int tremoloIndex; public: OneSecondTremoloEffect() : CXAPOBase(&RegistrationProps), tremoloIndex(0) { } protected: virtual HRESULT __stdcall LockForProcess( UINT32 inpParamCount, const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS *pInpParams, UINT32 outParamCount, const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS *pOutParam) override; virtual void __stdcall Process( UINT32 inpParameterCount, const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams, UINT32 outParamCount, XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams, BOOL isEnabled) override; }; class __declspec(uuid("6FB2EBA3-7DCB-4ADF-9335-686782C49911")) OneSecondTremoloEffect;
RegistrationProperties 字段被初始化 (快到了) 的代码文件中。 指向它的指针传递给 CXAPOBase 的构造函数。 在很多时候 CXAPOBase 导数将还定义字段类型 WAVEFORMATEX 的 (如同这一个) 或 WAVEFORMATEXTENSIBLE (在一般情况下) 为保存波形格式传递通过效果的音频流。
此外请注意 __declspec ("声明说明符") 底部的文件,将与一个 GUID 关联的 OneSecondTremoloEffect 类。 您可以为您自己的影响类从在 Visual Studio 中的工具菜单上的创建 GUID 选项生成的 GUID。
CXAPOBase 导数必须重写的过程方法和通常重写 LockForProcess 方法,以及。 LockForProcess 方法允许 APO 来执行初始化基于特定的音频格式,其中包括采样率、 通道和样本数据类型的数目。 过程方法实际上执行的分析或音频数据的修改。
图 5 显示了这两个方法,以及 RegistrationProperties 字段的初始化。 请注意 XAPO_REGISTRATION_PROPERTIES 的第一个字段是用类标识的 GUID。
图 5 OneSecondTremoloEffect.cpp 文件
#include "pch.h" #include "OneSecondTremoloEffect.h" const XAPO_REGISTRATION_PROPERTIES OneSecondTremoloEffect::RegistrationProps = { __uuidof(OneSecondTremoloEffect), L"One-Second Tremolo Effect", L"Coded by Charles Petzold", 1, // Major version number 0, // Minor version number XAPOBASE_DEFAULT_FLAG | XAPO_FLAG_INPLACE_REQUIRED, 1, // Min input buffer count 1, // Max input buffer count 1, // Min output buffer count 1 // Max output buffer count }; HRESULT OneSecondTremoloEffect::LockForProcess( UINT32 inpParamCount, const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS *pInpParams, UINT32 outParamCount, const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS *pOutParams) { waveFormat = * pInpParams[0].pFormat; return CXAPOBase::LockForProcess(inpParamCount, pInpParams, outParamCount, pOutParams); } void OneSecondTremoloEffect::Process(UINT32 inpParamCount, const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams, UINT32 outParamCount, XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams, BOOL isEnabled) { XAPO_BUFFER_FLAGS flags = pInpParams[0].BufferFlags; int frameCount = pInpParams[0].ValidFrameCount; const float * pSrc = static_cast<float *>(pInpParams[0].pBuffer); float * pDst = static_cast<float *>(pOutParams[0].pBuffer); int numChannels = waveFormat.nChannels; switch(flags) { case XAPO_BUFFER_VALID: for (int frame = 0; frame < frameCount; frame++) { float sin = 1; if (isEnabled) { sin = fabs(DirectX::XMScalarSin(DirectX::XM_PI * tremoloIndex / waveFormat.nSamplesPerSec)); tremoloIndex = (tremoloIndex + 1) % waveFormat.nSamplesPerSec; } for (int channel = 0; channel < numChannels; channel++) { int index = numChannels * frame + channel; pDst[index] = sin * pSrc[index]; } } break; case XAPO_BUFFER_SILENT: break; } pOutParams[0].ValidFrameCount = pInpParams[0].ValidFrameCount; pOutParams[0].BufferFlags = pInpParams[0].BufferFlags; }
在理论上,APOs 可以处理多个输入的缓冲区和多个输出缓冲区。 但是,当前仅限于一个输入的缓冲区和一个输出缓冲区 APOs。 这种限制影响的 XAPO_REGISTRATION_PROPERTIES 结构和 LockForProcess 和过程方法的参数的最后四个字段。 对于这两种方法,inpParamCount 和 outParamCount 总是等于 1,并且指针参数始终指向该指示结构的只是一个实例。
100 每秒调用的速度,APO 的过程方法接收音频数据输入的缓冲区和输出缓冲区进行准备。 它是可能为 APOs 执行格式转换 — — 例如,若要更改输入和输出缓冲区,或通道,数目或样本的数据类型之间的采样率。
这些格式转换可以很困难,所以您可以指示第六领域的 XAPO_REGISTRATION_PROPERTIES 结构的转换你不准备实施。 XAPOBASE_DEFAULT_FLAG 指示您不希望执行转换的采样率、 通道的数量、 样品的位大小或帧大小 (每个过程调用中的样本数)。
通过 APO 的音频数据的格式是从参数获得的 LockForProcess 重写标准的 WAVEFORMATEX 结构的形式。通常,LockForProcess 只调用一次。 大多数 APOs 需要知道的采样率和通道数和这是最好地概括你 APO 的任何可能的值。
同样关键的是示例本身的数据类型。 最常使用的 XAudio2 时,你面对的是 16 位整数或 32 位浮点值的样本。 在内部,然而,XAudio2 更喜欢使用浮点数据 (c + + 浮点类型),那是你会看到在您 APOs。 如果您愿意,您可以验证中的 LockForProcess 方法的示例数据类型。 然而,它也是我的 WAVEFORMATEX 结构的 wFormatTag 字段不等于 WAVE_FORMAT_IEEE_FLOAT,可能会按预期的那样的经验。 相反,它是 WAVE_FORMAT_EXTENSIBLE (值 65534),这意味着你真的在处理一个 WAVEFORMATEXTENSIBLE 结构,在其中案例子格式字段指示的数据类型 KSDATAFORMAT_SUBTYPE_IEEE_FLOAT。
如果在 LockForProcess 方法遇到它不能处理的音频格式,则应返回 HRESULT 指示错误,也许 E_NOTIMPL 表示"未实现"。
LockForProcess 方法可以花不管什么时候它需要进行初始化,但过程方法在线程上运行音频处理,和它必须不拖拖拉拉。 你就会发现为 44,100 Hz 采样率,ValidFrameCount 字段,指示过程称为每秒 100 次的缓冲区参数等于 441,每次以 10 ms 的音频数据。 为双声道的立体声音响,缓冲区包含 882 的浮点型值与交错的渠道:其次是右声道左的声道。
BufferFlags 字段是 XAPO_BUFFER_VALID 或 XAPO_BUFFER_SILENT。 此标志允许您跳过处理如果没有实际的音频数据,来了。 此外,启用参数,指示是否你已经看过的 EnableEffect 和 DisableEffect 的方法通过启用了这种效果。
如果该缓冲区是有效的 OneSecondTremoloEffect APO 循环通过帧和渠道,计算指数为缓冲区,并转让浮到目标缓冲区 (pDst) 的值的源缓冲区 (预应力钢骨混凝土)。 如果禁用了效果,因子为 1 被应用于源值。 如果启用它,则被应用正弦值,从 DirectX 数学库使用 zippy XMScalarSin 函数来计算。
过程方法的末尾,在 ValidFrameCount 和 BufferFlags 的输入的参数结构的对应值的输出参数结构上设置。
虽然代码将输入和输出缓冲区视为单独的对象,但这不是实际情况。 之间你可以在 XAPO_REGISTRATION_PROPERTIES 结构中设置的标志是 (其中包括在 XAPOBASE_DEFAULT_FLAG) 的 XAPO_FLAG_INPLACE_SUPPORTED 和 XAPO_FLAG_INPLACE_REQUIRED。 "就地"一词意味着对输入和输出缓冲区指针 — — 在我的代码中调用预应力钢骨混凝土和 pDst — — 实际上相等。 有只有一个缓冲区,用于输入和输出。 你绝对应该意识到这一事实在编写代码时。
但是请注意:这是我的经验如果删除了这些标志,单独的缓冲区是的确存在,但只输入的缓冲区是有效的两个输入和输出。
颤音效果只需要改变样本。 回声效果需要保存前面的示例,因为一秒回声效果的输出是当前音频再加上一秒钟前从音频。
这意味着 OneSecondEchoEffect 类需要维护它自己的缓冲区的音频数据,它在 LockForProcess 方法期间将定义为浮点类型和大小的方法:
delayLength = waveFormat.nSamplesPerSec; int numDelaySamples = waveFormat.nChannels * waveFormat.nSamplesPerSec;delayBuffer.resize(numDelaySamples);此 delayBuffer 向量是不足以容纳一秒的音频数据,,它被视为一个循环缓冲区。 LockForProcess 方法初始化为 0 值的缓冲区,并初始化此缓冲区的索引:
delayIndex = 0;
图 6 OneSecondEchoEffect 中显示进程的方法。 因为源音频已完成后,必须继续回声效果,你可以不再跳过处理时,XAPO_BUFFER_SILENT 标志表示没有输入的音频。 相反,该声音文件完成后,必须继续发挥回声末尾的音频输出。 变量命名源因此是输入的音频或值为 0,根据 XAPO_BUFFER_SILENT 标志的存在。 此源值的一半加一半的延迟缓冲区中存储的值和结果保存回入延迟缓冲区中。 在任何时候,你听到一半当前音频,加四分之一的音频从一秒钟前,八分之一的音频从两秒前等等。 您可以调整不同的效果,包括获取大声与每个重复的回声的平衡。
图 6 OneSecondEchoEffect 中的过程方法
void OneSecondEchoEffect::Process(UINT32 inpParamCount, const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams, UINT32 outParamCount, XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams, BOOL isEnabled) { const float * pSrc = static_cast<float *>(pInpParams[0].pBuffer); float * pDst = static_cast<float *>(pOutParams[0].pBuffer); int frameCount = pInpParams[0].ValidFrameCount; int numChannels = waveFormat.nChannels; bool isSourceValid = pInpParams[0].BufferFlags == XAPO_BUFFER_VALID; for (int frame = 0; frame < frameCount; frame++) { for (int channel = 0; channel < numChannels; channel++) { // Get sample based on XAPO_BUFFER_VALID flag int index = numChannels * frame + channel; float source = isSourceValid ? pSrc[index] : 0.0f; // Combine sample with contents of delay buffer and save back int delayBufferIndex = numChannels * delayIndex + channel; float echo = 0.5f * source + 0.5f * delayBuffer[delayBufferIndex]; delayBuffer[delayBufferIndex] = echo; // Transfer to destination buffer pDst[index] = isEnabled ? echo : source; } delayIndex = (delayIndex + 1) % delayLength; } pOutParams[0].BufferFlags = XAPO_BUFFER_VALID; pOutParams[0].ValidFrameCount = pInpParams[0].ValidFrameCount; }尝试设置延迟缓冲区的长度为十分之一的第二次:
delayLength = waveFormat.nSamplesPerSec / 10;
现在您得到更多的比一个独特的回声的混响效果。 当然,在真正的阿婆,你会想编程控制这些各种参数 (和其他人以及),这就是为什么真正的回声混响 APO 由 XAUDIO2FX_REVERB_PARAMETERS 结构与 23 字段控制。
大多数 APOs 允许其运行时的参数,可以以编程方式设置要更改的行为。 SetEffectParameters 方法为所有的语音类定义和引用特定的 APO 与索引。 设定的 APO 是有点棘手的实现,但不是多。
在本专栏的前一部分中,我演示了如何使用在 XAudio2 源和 submix 声音中实现内置带通滤波器来创建 26 波段图形均衡器,在其中每个带区会影响总的音频频谱的三分之一倍频程。 GraphicEqualizer 程序有效地将声音拆分成 26 部分的这些筛选器,应用程序,然后重新组合这些音频流。 这种技术可能似乎有些效率低下。
它是可能在单一的阿婆,实现整个图形均衡器算法,让前面的程序只是一个源声音和一个掌握声音的效果相同。这是我在 GraphicEqualizer2 程序中做了什么。 这个新的程序相同的外观和声音相同,早期版本的程序,但国内很大的不同。
在将参数传递给 APO 的问题之一是线程同步。 过程方法的音频处理线程中运行,并可能正在从 UI 线程设置参数。 幸运的是,CXAPOParametersBase 类为您执行此同步。
首先,您需要定义一个结构的参数。 26-波段均衡器效果,该结构包含是的 26 幅水平数组的只是一个字段:
struct OneThirdOctaveEqualizerParameters { std::array<float, 26> Amplitude;};
在程序内,此数组的成员的计算是从滑块的分贝值。
若要初始化 CXAPOParametersBase,您需要三个参数的结构的数组传递给其构造函数。CXAPOParametersBase 使用此内存块的执行的线程同步。
我们再次遇到问题的传递已初始化派生类对基类构造函数的数据。 我选择这个时候的解决方案是为受保护定义派生的类的构造函数并将从名为创建、 中所示的公共静态方法的类实例化图 7。
图 7 静态创建的方法为 OneThirdOctaveEqualizerEffect
OneThirdOctaveEqualizerEffect * OneThirdOctaveEqualizerEffect::Create() { // Create and initialize three effect parameters OneThirdOctaveEqualizerParameters * pParameterBlocks = new OneThirdOctaveEqualizerParameters[3]; for (int i = 0; i < 3; i++) for (int band = 0; band < 26; band++) pParameterBlocks[i].Amplitude[band] = 1.0f; // Create the effect return new OneThirdOctaveEqualizerEffect( &RegistrationProps, (byte *) pParameterBlocks, sizeof(OneThirdOctaveEqualizerParameters), false); }在 XAudio2 实施的数字二阶过滤器 (这仿效此 APO) 涉及以下公式:
y = (b0·x + b1·x’ + b2·x’’ – a1·y’ – a2·y’’) / a0
在此公式中,x 是输入样品,x' 是上面输入的示例中和 x ' 是在那之前的示例。 输出是 y,y' 是的以前的输出和 y ' 是在那之前的输出。
一种均衡器效果因而需要保存两个以前的输入的值,对于每个通道,和以前两个输出每个通道,每个带区的值。
在此公式中的六个常数取决于类型的筛选器 ; 截止频率 (或带通滤波器的中心频率) 与采样率 ; 和 Q,筛选器质量。 一-三-八度图形均衡器,每个筛选器具有相应的带宽,三分之一倍频程或 4.318 Q。 每个带区有一套独特的中所示的代码计算的 LockForProcess 方法中的常量图 8。
图 8 均衡器筛选器常数的计算
Q = 4.318f; // One-third octave static float frequencies[26] = { 20.0f, 25.0f, 31.5f, 40.0f, 50.0f, 63.0f, 80.0f, 100.0f, 125.0f, 160.0f, 200.0f, 250.0f, 320.0f, 400.0f, 500.0f, 630.0f, 800.0f, 1000.0f, 1250.0f, 1600.0f, 2000.0f, 2500.0f, 3150.0f, 4000.0f, 5000.0f, 6300.0f }; for (int band = 0; band < 26; band++) { float frequency = frequencies[band]; float omega = 2 * 3.14159f * frequency / waveFormat.nSamplesPerSec; float alpha = sin(omega) / (2 * Q); a0[band] = 1 + alpha; a1[band] = -2 * cos(omega); a2[band] = 1 - alpha; b0[band] = Q * alpha; // == sin(omega) / 2; b1[band] = 0; b2[band] = -Q * alpha; // == -sin(omega) / 2; }
期间的过程方法,APO 获取指向当前参数结构与调用 CXAPOParametersBase::BeginProcess,在这种情况下将返回值转换为一个结构类型爱抚的OctaveEqualizerParameters。 在结束的过程方法,对 CXAPOParametersBase::EndProcess 的调用释放的参数结构的方法等等。 所示的完整的过程方法图 9。
图 9 OneThirdOctaveEqualizerEffect 中的过程方法
void OneThirdOctaveEqualizerEffect::Process(UINT32 inpParamCount, const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParam, UINT32 outParamCount, XAPO_PROCESS_BUFFER_PARAMETERS *pOutParam, BOOL isEnabled) { // Get effect parameters OneThirdOctaveEqualizerParameters * pEqualizerParams = (OneThirdOctaveEqualizerParameters *) CXAPOParametersBase::BeginProcess(); // Get buffer pointers and other information const float * pSrc = static_cast<float *>(pInpParam[0].pBuffer); float * pDst = static_cast<float *>(pOutParam[0].pBuffer); int frameCount = pInpParam[0].ValidFrameCount; int numChannels = waveFormat.nChannels; switch(pInpParam[0].BufferFlags) { case XAPO_BUFFER_VALID: for (int frame = 0; frame < frameCount; frame++) { for (int channel = 0; channel < numChannels; channel++) { int index = numChannels * frame + channel; // Do very little if filter is disabled if (!isEnabled) { pDst[index] = pSrc[index]; continue; } // Get previous inputs float x = pSrc[index]; float xp = pxp[channel]; float xpp = pxpp[channel]; // Initialize accumulated value float accum = 0; for (int band = 0; band < 26; band++) { int bandIndex = numChannels * band + channel; // Get previous outputs float yp = pyp[bandIndex]; float ypp = pypp[bandIndex]; // Calculate filter output float y = (b0[band] * x + b1[band] * xp + b2[band] * xpp - a1[band] * yp - a2[band] * ypp) / a0[band]; // Accumulate amplitude-adjusted filter output accum += y * pEqualizerParams->Amplitude[band]; // Save previous output values pypp[bandIndex] = yp; pyp[bandIndex] = y; } // Save previous input values pxpp[channel] = xp; pxp[channel] = x; // Save final value adjusted for filter gain pDst[index] = accum / Q; } } break; case XAPO_BUFFER_SILENT: break; } // Set output parameters pOutParam[0].ValidFrameCount = pInpParam[0].ValidFrameCount; pOutParam[0].BufferFlags = pInpParam[0].BufferFlags; CXAPOParametersBase::EndProcess(); }
我一直喜欢的一个特点是编程的问题往往有多个解决方案。 有时不同的解决办法是以某种方式,效率更高,有时不。 当然 26 IXAudio2SubmixVoice 实例替换为单个的 APO 是彻底的改变。 但如果您认为此更改会反映在极大地改进了性能,你错了。 Windows 8 的任务管理器显示这两个 GraphicEqualizer 程序是大约相等,暗示将音频流分割成 26 submix 声音毕竟不那么疯狂。
Charles Petzold 是 MSDN 杂志和作者的"编程窗口,第 6 版"的长期贡献 (O'Reilly 媒体,2012年),一本关于编写应用程序的 Windows 8 书。 他的网站是 charlespetzold.com。
衷心感谢以下技术专家对本文的审阅:邓肯麦凯 (Microsoft) 和詹姆斯 McNellis (Microsoft)