WASAPI 捕获的数据包不对齐

Posted

技术标签:

【中文标题】WASAPI 捕获的数据包不对齐【英文标题】:WASAPI captured packets do not align 【发布时间】:2020-10-01 15:38:21 【问题描述】:

我正在尝试可视化 WASAPI 环回捕获的声波,但发现我记录的数据包放在一起时不会形成平滑的波。

我对 WASAPI 捕获客户端如何工作的理解是,当我调用 pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, NULL, NULL) 时,缓冲区 pData 从前面填充有 numFramesAvailable 数据点。每个数据点都是一个浮点数,它们按通道交替。因此,为了获得所有可用的数据点,我应该将 pData 转换为浮点指针,并获取第一个 channels * numFramesAvailable 值。一旦我释放缓冲区并再次调用GetBuffer,它就会提供下一个数据包。我会假设这些数据包会一个接一个,但事实并非如此。

我的猜测是,要么我对pData 中的音​​频数据格式做出错误假设,要么捕获客户端丢失或重叠帧。但不知道如何检查这些。

为了使下面的代码尽可能简短,我删除了错误状态检查和清理等内容。

捕获客户端的初始化:

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);

pAudioClient = NULL;
IMMDeviceEnumerator * pDeviceEnumerator = NULL;
IMMDevice * pDeviceEndpoint = NULL;
IAudioClient *pAudioClient = NULL;
IAudioCaptureClient *pCaptureClient = NULL;
int channels;
// Initialize audio device endpoint
CoInitialize(nullptr);
CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator );
pDeviceEnumerator ->GetDefaultAudioEndpoint(eRender, eConsole, &pDeviceEndpoint );

// init audio client
WAVEFORMATEX *pwfx = NULL;
REFERENCE_TIME hnsRequestedDuration = 10000000;
REFERENCE_TIME hnsActualDuration;

audio_device_endpoint->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient);
pAudioClient->GetMixFormat(&pwfx);

pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, hnsRequestedDuration, 0, pwfx, NULL);
channels = pwfx->nChannels;

pAudioClient->GetService(IID_IAudioCaptureClient, (void**)&pCaptureClient);
pAudioClient->Start();  // Start recording.

数据包捕获(注意std::mutex packet_buffer_mutexvector<vector<float>> packet_buffer 已被另一个线程定义并用于安全显示数据):

UINT32 packetLength = 0;
BYTE *pData = NULL;
UINT32 numFramesAvailable;
DWORD flags;
int max_packets = 8;

std::unique_lock<std::mutex>write_guard(packet_buffer_mutex, std::defer_lock);

while (true) 
    pCaptureClient->GetNextPacketSize(&packetLength);
    while (packetLength != 0)
    
        // Get the available data in the shared buffer.
        pData = NULL;
        pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, NULL, NULL);

        if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
        
            pData = NULL;  // Tell CopyData to write silence.
        

        write_guard.lock();
        if (packet_buffer.size() == max_packets) 
            packet_buffer.pop_back();
        

        if (pData) 
            float * pfData = (float*)pData;
            packet_buffer.emplace(packet_buffer.begin(), pfData, pfData + channels * numFramesAvailable);
         else 
            packet_buffer.emplace(packet_buffer.begin());
        
        write_guard.unlock();

        hpCaptureClient->ReleaseBuffer(numFramesAvailable);
        pCaptureClient->GetNextPacketSize(&packetLength);
    
    std::this_thread::sleep_for(std::chrono::milliseconds(10));

我将数据包存储在 vector&lt;vector&lt;float&gt;&gt;(其中每个 vector&lt;float&gt; 是一个数据包)中,删除最后一个并在开头插入最新的,以便我可以按顺序遍历它们。 下面是捕获的正弦波的结果,绘制了交替值,因此它仅代表单个通道。很清楚数据包在哪里被缝合在一起。

【问题讨论】:

您似乎复制了代码from here。缺少 SetFormat() 调用,不好。 不是 SetFormat 只是一个用户定义的函数,它通知如何复制数据,这是我在将数据包转换为 vector&lt;float&gt; 时自己处理的? 您多久返回一次AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY flags 【参考方案1】:

某些东西正在向 Windows 播放正弦波;您正在音频环回中记录正弦波;你得到的正弦波并不是真正的正弦波。

您几乎肯定会遇到故障。最可能的故障原因是:

向 Windows 播放正弦波的任何东西都没有及时将数据发送到 Windows,因此缓冲区正在干涸。 无论是从 Windows 中读取环回数据,都没有及时读取数据,因此缓冲区已满。 在将正弦波播放到 Windows 和读取回来之间出现问题。

可能不止一种情况正在发生。

IAudioCaptureClient::GetBuffer 调用会告诉您是否为时已晚读取数据。特别是它将设置*pdwFlags,以便设置AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY 位。

查看您的代码,我看到您在 GetBuffer 和 WriteBuffer 之间做了以下事情:

等待锁定 有时会做一些叫做“pop_back”的事情 做一些叫做“emplace”的事情

我引用上面链接的文档:

客户端应避免获取数据包的 GetBuffer 调用和释放数据包的 ReleaseBuffer 调用之间的过度延迟。音频引擎的实现假定 GetBuffer 调用和相应的 ReleaseBuffer 调用发生在相同的缓冲处理周期内。延迟释放数据包超过一段时间的客户端可能会丢失样本数据。

特别是,您永远不应该在GetBufferReleaseBuffer 之间进行以下任何操作,因为它们最终会导致故障:

等待锁定 等待任何其他操作 读取或写入文件 分配内存

相反,在调用IAudioClient::Start 之前预先分配大量内存。当每个缓冲区到达时,写入此内存。另一方面,有一个定期安排的工作项,该工作项占用写入的内存并将其写入磁盘或您正在使用它执行的任何操作。

【讨论】:

std::vector::emplace 可能是内存分配,具体取决于std::vector::capacity。由于该示例没有在GetBuffer/ReleaseBuffer 范围之外保留额外容量的路径,这意味着向量偶尔会增长。但最糟糕的部分可能是锁;像这样的小内存分配非常快。

以上是关于WASAPI 捕获的数据包不对齐的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 Win32 WASAPI C++ 中的“捕获流”创建 wav 文件

WASAPI 在 Windows 上捕获的缓冲区大小

修改系统卷时如何修改 WASAPI 环回捕获卷?

渲染音频流(WASAPI / WINAPI)

WASAPI + windows 商店应用初始化

为啥 TCP 数据包不包含源 IP 和目标 IP?