Захваченные пакеты WASAPI не выравниваются

#c #windows #audio #wasapi

#c #Windows #Аудио #wasapi

Вопрос:

Я пытаюсь визуализировать звуковую волну, захваченную циклом WASAPI, но обнаружил, что записываемые мной пакеты не образуют плавную волну при объединении.

Мое понимание того, как работает клиент захвата WASAPI, заключается в том, что при вызове pCaptureClient->GetBuffer(amp;pData, amp;numFramesAvailable, amp;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**)amp;pDeviceEnumerator );
pDeviceEnumerator ->GetDefaultAudioEndpoint(eRender, eConsole, amp;pDeviceEndpoint );

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

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

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

pAudioClient->GetService(IID_IAudioCaptureClient, (void**)amp;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(amp;packetLength);
    while (packetLength != 0)
    {
        // Get the available data in the shared buffer.
        pData = NULL;
        pCaptureClient->GetBuffer(amp;pData, amp;numFramesAvailable, amp;flags, NULL, NULL);

        if (flags amp; 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(amp;packetLength);
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
  

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

Комментарии:

1. Похоже, вы скопировали код отсюда . Вызов SetFormat() отсутствует, что плохо.

2. разве SetFormat не является просто определяемой пользователем функцией, которая сообщает, как копировать данные, с чем я справляюсь сам, когда конвертирую пакет в a vector<float> ?

3. Как часто вы 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 происходят в течение одного и того же периода обработки буфера. Клиенты, которые задерживают отправку пакета более чем на один период, рискуют потерять образцы данных.

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

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

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

Комментарии:

1. std::vector::emplace может быть выделение памяти, в зависимости от std::vector::capacity . Поскольку в примере нет пути для резервирования дополнительной емкости за пределами GetBuffer / ReleaseBuffer span , это означает, что вектор будет иногда расти. Но худшей частью, вероятно, является блокировка; небольшое выделение памяти, подобное этому, происходит довольно быстро.