> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 使用 XDSP API 进行带包络的单流卷积

> 使用 XDSP API 进行带包络的单流卷积概述

在本主题中，我们将通过 Microsoft Game Development Kit (GDK) 附带的一个示例，逐步介绍在 XBOX Series X 设备上使用硬件卷积混响的预期代码流程。

## 硬件连接与启动

必须通过 [XDspConnect](/reference/audio/xdspaudio/functions/xdspconnect) 或 [XDspConnectWithMaximumStreamLimit](/reference/audio/xdspaudio/functions/xdspconnectwithmaximumstreamlimit) 建立与硬件加速单元的连接。调用者使用以下参数设置连接的要求：

1. *baseBuffer* - 指向用户分配的一块内存，其大小足以容纳将传给硬件的输入、输出、包络缓冲区和/或块乘子缓冲区 (block multiplier buffers)。该内存应通过 XMemAlloc 分配，属性为：*XALLOC\_MEMTYPE\_PHYSICAL\_CACHEABLE*、*XALLOC\_PAGESIZE\_64KB*、*XALLOC\_ALIGNMENT\_64K*。这些属性应通过 `MAKE_XALLOC_ATTRIBUTES()` 设置，示例见下文。该内存在 [XDspDisconnect](/reference/audio/xdspaudio/functions/xdspdisconnect) 调用成功之前不能释放。此缓冲区至少 64KB。

2. *baseBufferLength* - *baseBuffer* 的长度（字节数）。此缓冲区至少 64KB，且不超过 500 MB。

3. *aggregateImpulseResponseInSeconds* - 代表将通过 [XDspActivate](/reference/audio/xdspaudio/functions/xdspactivate) 与 [XDspProcessType::Convolution](/reference/audio/xdspaudio/enums/xdspprocesstype) 同时激活的所有单独脉冲响应的总时长。如果此值为 0，则不能激活任何 [XDspProcessType::Convolution](/reference/audio/xdspaudio/enums/xdspprocesstype) 类型的流。

4. handle - 指向 [XDspClientHandle](/reference/audio/xdspaudio/handles/xdspclienthandle) 的指针，用于接收本次调用返回的句柄。

如果调用成功，调用者即可通过 [XDspCommand](/reference/audio/xdspaudio/structs/xdspcommand) 参数使用 *baseBuffer* 向硬件传入输入数据并获取流的输出数据。传给 [XDspCommand](/reference/audio/xdspaudio/structs/xdspcommand) 的任何缓冲区都必须按 16 字节对齐。以下是一个可用于管理输入输出缓冲区的简单缓冲区管理器示例。

```cpp theme={null}
class ConvolveOneBufferManager
{
private:
    float const* _outputBuffer = nullptr;
    uint32_t _maxBufferFrameCount = 0;
    uint32_t _bufferStride = 0;
    uint32_t _maxBufferCount = 0;
    uint32_t _acquiredBufferCount = 0;
    uint32_t _channelCount = 1;

public:
    void Reset(float const* outputBuffer, uint32_t maxBufferCount, uint32_t maxBufferFrameCount, uint32_t channelCount) {
        _outputBuffer = outputBuffer;
        _maxBufferCount = maxBufferCount;
        _maxBufferFrameCount = maxBufferFrameCount;
        _channelCount = channelCount;
    }

    uint32_t GetMaxBufferFrameCount() const {
        return _maxBufferFrameCount;
    }

    bool IsFull() const {
        return _acquiredBufferCount >= _maxBufferCount;
    }

    void Acquire() {
        if (_acquiredBufferCount < _maxBufferCount) {
            _acquiredBufferCount++;
        }
    }

    void Release() {
        if (_acquiredBufferCount > 0) {
            _acquiredBufferCount--;
        }
    }

    float* GetOutputBuffer(uint32_t index) {
        return const_cast<float*>(_outputBuffer) + (((index % _maxBufferCount) * _maxBufferFrameCount * _channelCount));
    }

};
```

每个流的包络缓冲区应与输入/输出缓冲区类似，属于传给 **XDspConnect** 或 **XDspConnectWithMaximumStreamLimit** 的 baseBuffer 的一部分。每个包络缓冲区的长度可按如下方式计算。

```cpp theme={null}
#define ROUND_UP_TO_16BYTE_ALIGNED(x) (((x) + (15)) & ~(0xF))
uint32_t blockSize = 1024; // can be 512 or 1024
uint32_t irLengthInComplexes = impulseResponseLengthInFloats/2;
uint32_t numFilterBlocks = irLengthInComplexes/blockSize;

// In case of a stereo impulse response filter, the envelope buffer length will be 2 * envelopeLengthInBytes assuming impulseResponseLengthInFloats is the impule response length of a single channel.
uint32_t envelopeLengthInBytes = ROUND_UP_TO_16BYTE_ALIGNED(numFilterBlocks * sizeof(float));
uint32_t envelopeGainCount = envelopeLengthInBytes/sizeof(float);
```

以下是分配内存并建立连接的第一步代码示例。请注意，如果可以处于活动状态的流数量少于 256，可以用 **XDspConnectWithMaximumStreamLimit** 代替 **XDspConnect** 以减少内部内存使用。

```cpp theme={null}
static const ULONGLONG XMemAllocAttributes = MAKE_XALLOC_ATTRIBUTES(allocatorId,
                      0,
                      XALLOC_MEMTYPE_PHYSICAL_CACHEABLE,
                      XALLOC_PAGESIZE_64KB,
                      XALLOC_ALIGNMENT_64K,
                      FALSE);

float* baseBuffer = (float *)XMemAlloc(baseBufferLength, XMemAllocAttributes);
```

其中 XMemAllocAttributes 表示上述属性。下列代码将 *aggregateImpulseResponse* 设为 32 秒。

```cpp theme={null}
hr = XDspConnect(baseBuffer, baseBufferLength, 32 /*aggregateImpulseResponseInSeconds*/, &clientHandle);
```

## 流的激活与停用

在与设备建立连接后，调用者应发送一个流激活命令以启用卷积、FFT 或 IFFT。调用者必须在 [XDspActivationParameters](/reference/audio/xdspaudio/structs/xdspactivationparameters) 中指定 [XDspProcessType](/reference/audio/xdspaudio/enums/xdspprocesstype)、块帧数、声道数、以浮点数表示的脉冲响应长度以及指向频域脉冲响应数据的指针。只允许单声道或立体声流。对于 [XDspProcessType::ForwardFourierTransform](/reference/audio/xdspaudio/enums/xdspprocesstype) 和 [XDspProcessType::inverseFourierTransform](/reference/audio/xdspaudio/enums/xdspprocesstype)，[XDspActivationParameters::impulseResponse](/reference/audio/xdspaudio/structs/xdspactivationparameters) 应为 nullptr，且 [XDspActivationParameters::impulseResponseLengthInFloats](/reference/audio/xdspaudio/structs/xdspactivationparameters) = 0。[XDspActivationOptions](/reference/audio/xdspaudio/enums/xdspactivationoptions) 应做相应设置。

以下是激活一个卷积流的代码。

```cpp theme={null}
XDspActivationParameters params;
XDspStatus* status;
XDspStreamHandle* streamHandle;
params.type = XDspProcessType::Convolution;
params.blockFrameCount = maxBufferFrameCount;
params.channelCount = channelCount; // mono or stereo
params.impulseResponseLengthInFloats = filterFrameCount;
params.impulseResponse = filterBuffer;

if (channelCount == 2 && deinterleaved)
{
    params.options |= XDspActivationOptions::Deinterleaved;
}
if (stereoImpulseResponse)
{
    params.options |= XDspActivationOptions::StereoImpulseResponse;
}
hr = XDspActivate(clientHandle, &params, &status, &streamHandle); 
```

[XDspActivate](/reference/audio/xdspaudio/functions/xdspactivate) 会返回一个 [XDspStatus](/reference/audio/xdspaudio/structs/xdspstatus) 缓冲区作为其参数之一。该 [XDspStatus](/reference/audio/xdspaudio/structs/xdspstatus) 缓冲区可用于查询硬件为该流处理的命令的状态。收到激活结果后，调用者即可开始提交用于卷积/FFT/IFFT 的数据。这应通过 [XDspSubmitCommand](/reference/audio/xdspaudio/functions/xdspsubmitcommand) 或 [XDspSubmitCommandWithEnvelope](/reference/audio/xdspaudio/functions/xdspsubmitcommandwithenvelope) API 完成。每次调用时，调用者必须：

1. 用恰好 *blockFrameCount* 帧数据填充输入缓冲区。
2. 用合适的值以及按 16 字节对齐的输入/输出缓冲区填充 [XDspCommand](/reference/audio/xdspaudio/structs/xdspcommand)。
3. 调用 [XDspSubmitCommand](/reference/audio/xdspaudio/functions/xdspsubmitcommand) 将命令提交给硬件。该函数会返回一个命令序号，可用于跟踪已发送给硬件的命令数量。
4. 若要应用包络，请在包络缓冲区中填入脉冲响应滤波器每个块的增益值，并调用 [XDspSubmitCommandWithEnvelope](/reference/audio/xdspaudio/functions/xdspsubmitcommandwithenvelope)；继续用同一个包络缓冲区调用，直到不再需要包络或需要更换包络。
5. 当不再需要包络时，请以 nullptr 作为 *envelopeBuffer* 调用 [XDspSubmitCommand](/reference/audio/xdspaudio/functions/xdspsubmitcommand) 或 [XDspSubmitCommandWithEnvelope](/reference/audio/xdspaudio/functions/xdspsubmitcommandwithenvelope)。请注意，*envelopeBuffer* 为 nullptr 的 **XDspSubmitCommandWithEnvelope** 等价于 **XDspSubmitCommand**。
6. 切换包络时，只有当硬件处理完使用当前 *envelopeBuffer* 的所有命令且结果已被领取后，才可将当前 *envelopeBuffer* 更新为下一个包络内容。如果尚未处理完，请使用另一个包络缓冲区。

```cpp theme={null}
XDspCommand command;

command.beginScale = 1.0f;
command.endScale = 1.0f;
command.inputBlockBuffer = inputDataBuffer;
command.outputBlockBuffer =  bufferManager.GetOutputBuffer(_totalCommandsSubmitted + 1);
command.blockBufferMultiplier = nullptr;

uint32_t sequence = 0;
hr = XDspSubmitCommand(streamHandle, &command, &sequence);

if (SUCCEEDED(hr))
{
    totalCommandsSubmitted++;
}
```

下列代码片段展示了如何使用脉冲响应滤波器每个块的增益值来填充包络缓冲区。示例演示了如何仅使用脉冲响应滤波器的前半部分：前半部分块的增益值从 1.0f 开始线性递减，后半部分则用 0.0f 填充。对于立体声脉冲响应滤波器，左声道的完整滤波器之后紧跟着右声道的滤波器——立体声脉冲响应滤波器的包络缓冲区遵循同样的模式：整个左声道的增益值之后紧跟着整个右声道的增益值。有关包络缓冲区大小的计算方式，请参见 [使用 XDSP API 的包络概述](/build/console-features/audio/overviews/xdsp-overview-enveloping)。

```cpp theme={null}
// Please see the code snippet at the beginning of this example for information on the envelope buffer size and envelope gain counts.
void CreateEnvelopeParams(
    float* envelopeBuffer, 
    uint32_t envelopeGainCount, 
    uint32_t numFilterBlocks,
    bool stereoImpulseResponse)
{
    const uint32_t envelopeRatio = 0.5f; // Use only half of the impulse response filter
    uint32_t numGains = (uint32_t)(envelopeRatio * numFilterBlocks);
    float gainDecrement = 1.0f/numGains;

    envelopeBuffer[0] = 1.0f;
        
    for (uint32_t i = 1; i < envelopeGainCount; i++)
    {
        if (i < numGains)
        {
            envelopeBuffer[i] = envelopeBuffer[i-1] - gainDecrement;
        }
        else
        {
            envelopeBuffer[i] = 0.0f;
        }
    }

    if (stereoImpulseResponse)
    {
        // Populate gain values for the right channel envelope
        memcpy((void*)&envelopeBuffer[envelopeGainCount], (void*)&envelopeBuffer[0], envelopeGainCount * sizeof(float));
    }
}
```

下列代码片段展示了通过传入有效的 *envelopeBuffer* 调用 **XDspSubmitCommandWithEnvelope** 来应用包络。除了额外传入 *envelopeBuffer* 参数外，此片段与使用 **XDspSubmitCommand** 提交命令的示例相同。应重复此过程，直到不再需要包络；届时应改为调用 **XDspSubmitCommand**，或者以 nullptr 作为 *envelopeBuffer* 调用 **XDspSubmitCommandWithEnvelope**。

```cpp theme={null}
XDspCommand command;

command.beginScale = 1.0f;
command.endScale = 1.0f;
command.inputBlockBuffer = inputDataBuffer;
command.outputBlockBuffer =  bufferManager.GetOutputBuffer(_totalCommandsSubmitted + 1);
command.blockBufferMultiplier = nullptr;

uint32_t sequence = 0;
hr = XDspSubmitCommandWithEnvelope(streamHandle, &command, envelopeBuffer, &sequence);

if (SUCCEEDED(hr))
{
    totalCommandsSubmitted++;
}
```

之后，可按如下方式检查 [XDspStatus](/reference/audio/xdspaudio/structs/xdspstatus)，以获取已提交给硬件的命令的状态：

```cpp theme={null}
while (status->lastProcessedCommandSequence > totalResponsesReceived)
{
    totalResponsesReceived++;
 
    // Hardware status is only updated when there is a hardware fault and the
    // error is going to persist for the duration of the stream. But, all the
    // commands that have already been submitted will still be processed by
    // the hardware and we have to wait for these commands to be processed 
    // before calling XDspDeactivate.
    hr = status->result; 
     
    float* outBuffer = bufferManager.GetOutputBuffer(totalResponsesReceived);
    // The output will be in the outBuffer
    // Do something with the outBuffer
    bufferManager.Release();
}
```

当流中的数据包处理完毕，或硬件在 [XDspStatus](/reference/audio/xdspaudio/structs/xdspstatus) 结果中返回错误时，应调用 [XDspDeactivate](/reference/audio/xdspaudio/functions/xdspdeactivate)。

```cpp theme={null}
while (totalResponsesReceived < totalCommandsSubmitted)
{
    // run the above code in the while loop to obtain all the responses
}
hr = XDspDeactivate(streamHandle);
```

如果硬件尚未完成本流所有已提交命令的处理，[XDspDeactivate](/reference/audio/xdspaudio/functions/xdspdeactivate) 会返回 *XDSP\_E\_PENDING\_RESULTS*。

## 终止

[XDspDeactivate](/reference/audio/xdspaudio/functions/xdspdeactivate) 成功后，调用者必须调用 [XDspDisconnect](/reference/audio/xdspaudio/functions/xdspdisconnect) 断开与音频硬件加速单元的连接，以释放所有已分配的资源。如果并非所有流都已停用，[XDspDisconnect](/reference/audio/xdspaudio/functions/xdspdisconnect) 会返回 *XDSP\_E\_NOT\_ALL\_HANDLES\_DEACTIVATED* 错误。

```cpp theme={null}
hr = XDspDisconnect(clientHandle);
XMemFree(baseBuffer, XMemAllocAttributes);
```

## 参考 API 文档

* [XDspAudio (API 内容)](/reference/audio/xdspaudio/xdspaudio_members)
  * Functions
    * [XDspConnect](/reference/audio/xdspaudio/functions/xdspconnect)
    * [XDspConnectWithMaximumStreamLimit](/reference/audio/xdspaudio/functions/xdspconnectwithmaximumstreamlimit)
    * [XDspDisconnect](/reference/audio/xdspaudio/functions/xdspdisconnect)
    * [XDspActivate](/reference/audio/xdspaudio/functions/xdspactivate)
    * [XDspSubmitCommand](/reference/audio/xdspaudio/functions/xdspsubmitcommand)
    * [XDspSubmitCommandWithEnvelope](/reference/audio/xdspaudio/functions/xdspsubmitcommandwithenvelope)
    * [XDspDeactivate](/reference/audio/xdspaudio/functions/xdspdeactivate)
  * Structures
    * [XDspCommand](/reference/audio/xdspaudio/structs/xdspcommand)
    * [XDspActivationParameters](/reference/audio/xdspaudio/structs/xdspactivationparameters)
    * [XDspStatus](/reference/audio/xdspaudio/structs/xdspstatus)

## 另请参阅

[XDSP 概述](/build/console-features/audio/overviews/xdsp-overview)
[使用 XDSP API 的包络概述](/build/console-features/audio/overviews/xdsp-overview-enveloping)


## Related topics

- [XDSP 包络概述](/zh-CN/build/console-features/audio/overviews/xdsp-overview-enveloping.md)
- [XDspConnect](/zh-CN/reference/audio/xdspaudio/functions/xdspconnect.md)
- [XDspSubmitCommand](/zh-CN/reference/audio/xdspaudio/functions/xdspsubmitcommand.md)
- [XDspConnectWithMaximumStreamLimit](/zh-CN/reference/audio/xdspaudio/functions/xdspconnectwithmaximumstreamlimit.md)
- [XDspCommand](/zh-CN/reference/audio/xdspaudio/structs/xdspcommand.md)
