WASAPI捕获的数据包不对齐

问题描述

我正在尝试可视化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,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,(void**)&pAudioClient);
pAudioClient->GetMixFormat(&pwfx);

pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,AUDCLNT_STREAMFLAGS_LOOPBACK,hnsRequestedDuration,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,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<vector<float>>(每个vector<float>一个数据包)中,删除最后一个数据包,并在开始处插入最新的数据包,以便按顺序遍历它们。 下面是捕获的正弦波的结果,绘制了交替的值,因此它仅代表一个通道。很明显,包在哪里缝合在一起。

Sound wave

解决方法

某些事情正在对Windows产生正弦波。您正在音频环回中记录正弦波;而您返回的正弦波并不是真正的正弦波。

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

  • 无论对Windows产生什么正弦波,都无法及时将数据发送到Windows,因此缓冲区正在耗尽。
  • 从Windows读取回送数据的任何操作都无法及时读取数据,因此缓冲区已满。
  • 在向Windows播放正弦波并回读正弦波之间出现了问题。

可能有不止一种情况发生。

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

看看您的代码,我发现您正在执行GetBuffer和WriteBuffer之间的以下操作:

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

我引用了以上链接的文档:

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

尤其是,GetBufferReleaseBuffer之间的任何操作都不应该做,因为它们最终会导致故障:

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

相反,请在调用IAudioClient::Start之前预先分配一堆内存。当每个缓冲区到达时,写入该存储器。在侧面,有一个定期计划的工作项,该工作项会占用已写入的内存,并将其写入磁盘或您正在使用它进行的任何操作。