Захваченные 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, 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_mutex и vector<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<vector<float>> (где каждый vector<float> является пакетом), удаляя последний и вставляя самый новый в начале, чтобы я мог перебирать их по порядку. Ниже показан результат захваченной синусоиды с чередующимися значениями, поэтому он представляет только один канал. Понятно, где пакеты сшиваются. Звуковая волна


person KyleL    schedule 01.10.2020    source источник
comment
Похоже, вы скопировали код отсюда . Вызов SetFormat() отсутствует, это нехорошо.   -  person Hans Passant    schedule 01.10.2020
comment
не является ли SetFormat просто определяемой пользователем функцией, которая сообщает, как копировать данные, что-то, что я делаю сам, когда преобразовываю пакет в vector<float>?   -  person KyleL    schedule 01.10.2020
comment
Как часто вы AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY возвращали flags   -  person Roman R.    schedule 19.10.2020


Ответы (1)


Что-то воспроизводит синусоиду в Windows; вы записываете синусоидальную волну обратно в звуковую петлю; и синусоидальная волна, которую вы получаете, на самом деле не синусоида.

Вы почти наверняка сталкиваетесь с глюками. Наиболее вероятные причины глюков:

  • Что бы ни воспроизводило синусоиду в Windows, данные в Windows не поступают вовремя, поэтому буфер иссякает.
  • Что бы ни считывало петлевые данные из Windows, они не считываются вовремя, поэтому буфер заполняется.
  • Что-то идет не так между воспроизведением синусоидальной волны в Windows и ее чтением.

Возможно, что происходит более одного из них.

IAudioCaptureClient::GetBuffer вызов сообщит вам, если вы прочитали данные слишком поздно. В частности, он установит *pdwFlags, чтобы был установлен бит AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY.

Глядя на ваш код, я вижу, что вы делаете следующие вещи между GetBuffer и WriteBuffer:

  • Ожидание блокировки
  • Иногда делаю что-то под названием pop_back
  • Делать что-то под названием emplace

Я цитирую документацию по ссылке выше:

Клиентам следует избегать чрезмерных задержек между вызовом GetBuffer, который получает пакет, и вызовом ReleaseBuffer, который освобождает пакет. Реализация звукового механизма предполагает, что вызов GetBuffer и соответствующий вызов ReleaseBuffer происходят в течение одного и того же периода обработки буфера. Клиенты, которые задерживают выпуск пакета более чем на один период, рискуют потерять образцы данных.

В частности, вы НИКОГДА НЕ ДОЛЖНЫ ДЕЛАТЬ НИЧЕГО ИЗ СЛЕДУЮЩЕГО между GetBuffer и ReleaseBuffer, потому что в конечном итоге они вызовут сбой:

  • Подождите на замке
  • Подождите любую другую операцию
  • Чтение или запись в файл
  • Выделить память

Вместо этого предварительно выделите кучу памяти перед вызовом IAudioClient::Start. По мере поступления каждого буфера записывайте в эту память. Кроме того, у вас есть регулярно запланированный рабочий элемент, который берет записанную память и записывает ее на диск или что-то еще, что вы делаете с ней.

person Matthew van Eerde    schedule 19.11.2020