Захват шлейфа WASAPI возвращает 0 байтов от Bluetooth-гарнитуры во время двунаправленного аудио (видеовызова), несмотря C++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Захват шлейфа WASAPI возвращает 0 байтов от Bluetooth-гарнитуры во время двунаправленного аудио (видеовызова), несмотря

Сообщение Anonymous »

Я хочу записать системный звук с помощью кольцевой записи WASAPI ( https://learn.microsoft.com/en-us/windo ... -recording ).
Я создал небольшое приложение, которое перечисляет доступные устройства и активные на них сеансы. Затем приложение записывает несколько секунд звука и сохраняет его в WAV-файл.
Кажется, это работает хорошо, но проблема возникает с Bluetooth-гарнитурами во время видеовызова.
Что работает
  • Запись с проводной гарнитуры
  • Запись с гарнитуры 2,4 ГГц
  • Запись с встроенный динамик ноутбука
  • Запись через Bluetooth-гарнитуру при использовании только динамика.
Что не получается
Когда Bluetooth-гарнитура используется как микрофон и динамик в видеовызове (например, вызов Chrome/WebRTC), петлевой захват на этом устройстве создает нулевые байты аудио, даже если счетчик сеанса показывает, что устройство имеет активный сеанс.
Что я вижу в своих журналах
Во время тестового звонка (Chrome на собрании) мой счетчик устройств рендеринга печатает что-то вроде этого:

Код: Выделить всё

=== Render Devices ===
[0] Headphones (OnePlus Buds 3) : 0 active sessions
ID: {0.0.0.00000000}.{19c0ada4-d680-46a9-919c-dca80bee9807}
[1] Speakers (Realtek(R) Audio) : 0 active sessions
ID: {0.0.0.00000000}.{357dc357-13e3-4cdb-90c9-ab54f23dedcf}
[2] XG27ACS (Intel(R) Display Audio) : 0 active sessions
ID: {0.0.0.00000000}.{4af2ff2a-e7bf-4a47-8de4-876e9ba2d6ce}
[3] CABLE Input (VB-Audio Virtual Cable) : 0 active sessions
ID: {0.0.0.00000000}.{65fbe74c-81c9-4c39-8a75-55033aa39f4c}
[4] CABLE In 16ch (VB-Audio Virtual Cable) : 0 active sessions
ID: {0.0.0.00000000}.{a2a0ed57-95c3-4689-b0ff-efabb8c3efe1}
[5] Headphones (2- Razer BlackShark V2 HS BT) [Default: Console, Multimedia, Communications] : 1 active session
ID: {0.0.0.00000000}.{dd3f0c1e-aa79-46ff-a3f2-528034df115d}
- chrome.exe (PID: 19412)

Затем начинается запись для активного устройства:

Код: Выделить всё

=== Active Devices (1) ===
[0] Headphones (2- Razer BlackShark V2 HS BT)
File: Headphones_2-_Razer_BlackShark_V2_HS_BT_13fa25c5.wav
Sessions: 1
[0] Headphones (2- Razer BlackShark V2 HS BT)
File: Headphones_2-_Razer_BlackShark_V2_HS_BT_13fa25c5.wav
Mix format: 44100 Hz, 32-bit, 2 channels

=== Starting Captures ===
Recording from 1 device(s)...

=== Stopping Captures ===
[OK] Headphones (2- Razer BlackShark V2 HS BT): 0.00 MB

=== Verifying Captures ===
[WARN] Headphones (2- Razer BlackShark V2 HS BT): Possible silence/issues

Итак:
  • Я вижу устройство Bluetooth-гарнитуры.
  • Я вижу на нем активный сеанс (Chrome, через IAudioSessionManager2 /
    IAudioSessionControl2).
  • Я успешно инициализирую петлевой захват именно в этой конечной точке.
  • Но в потоке обратной связи никакие данные не поступают (0) байт на всю
    длительность).
  • IAudioCaptureClient::GetNextPacketSize последовательно возвращает 0, и
    событие готовности к выборке никогда не сигнализируется.
Если я повторю тот же тест, используя:
  • Проводную гарнитуру или
  • A 2,4 ГГц USB-гарнитура или
  • Та же Bluetooth-гарнитура, но воспроизводит музыку/видео (без использования микрофона каким-либо приложением),
тогда работает петлевой захват, и я получаю действительные данные WAV.
Код: перечисление устройств и обнаружение сеанса
Вот как я перечисляю устройства и обнаруживаю активные сеансы:

Код: Выделить всё

// Enumerate render devices
wil::com_ptr deviceEnum;
CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
IID_PPV_ARGS(deviceEnum.put()));

wil::com_ptr deviceCollection;
deviceEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
deviceCollection.put());

UINT deviceCount = 0;
deviceCollection->GetCount(&deviceCount);

for (UINT i = 0; i < deviceCount; i++) {
wil::com_ptr device;
deviceCollection->Item(i, device.put());

// Get device ID
wil::unique_cotaskmem_string deviceId;
device->GetId(&deviceId);

// Get session manager to count active sessions
wil::com_ptr sessionMgr;
device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,
nullptr, (void**)sessionMgr.put());

wil::com_ptr  sessionEnum;
sessionMgr->GetSessionEnumerator(sessionEnum.put());

int sessionCount = 0;
sessionEnum->GetCount(&sessionCount);

// Count active sessions (excluding system sounds if desired)
int activeSessions = 0;
for (int j = 0; j < sessionCount; j++) {
wil::com_ptr session;
sessionEnum->GetSession(j, session.put());

AudioSessionState state;
session->GetState(&state);

if (state == AudioSessionStateActive) {
activeSessions++;
}
}

// If device has active sessions, capture from it
if (activeSessions > 0) {
StartLoopbackCapture(deviceId.get());
}
}

Как открыть петлевой захват
Код захвата представляет собой стандартный петлевой контроль WASAPI:
  • Получите IMMDevice по идентификатору с помощью IMMDeviceEnumerator::GetDevice.
  • IMMDevice::Activate, чтобы получить IAudioClient.
  • IAudioClient::GetMixFormat, чтобы получить формат микса устройства.
  • IAudioClient::Initialize в общем режиме с флагами обратной связи:

    Код: Выделить всё

    hr = audioClient->Initialize(
    AUDCLNT_SHAREMODE_SHARED,
    AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
    hnsBufferDuration,
    0,
    mixFormat,
    nullptr);
    
    
Мой цикл захвата:

Код: Выделить всё

// Set up event callback
HANDLE sampleReadyEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
audioClient->SetEventHandle(sampleReadyEvent);

// Get capture client
wil::com_ptr captureClient;
audioClient->GetService(__uuidof(IAudioCaptureClient),
(void**)captureClient.put());

// Start capture
audioClient->Start();

// Capture loop
while (capturing) {
DWORD waitResult = WaitForSingleObject(sampleReadyEvent, 1000);

// Process all available packets
UINT32 packetLength = 0;
while (SUCCEEDED(captureClient->GetNextPacketSize(&packetLength))
&& packetLength > 0) {
BYTE* pData = nullptr;
UINT32 numFrames = 0;
DWORD flags = 0;

hr = captureClient->GetBuffer(&pData, &numFrames, &flags,
nullptr, nullptr);
if (SUCCEEDED(hr)) {
UINT32 bytesToWrite = numFrames * mixFormat->nBlockAlign;

if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT)) {
WriteFile(hFile, pData, bytesToWrite, &bytesWritten, nullptr);
totalBytes += bytesWritten;   // Always 0 for Bluetooth during calls
}

captureClient->ReleaseBuffer(numFrames);
}
}
}

audioClient->Stop();

Что я уже пробовал
  • Подтвердил, что сеанс виден и активен через IAudioSessionManager2 на том же устройстве, которое я открываю для обратной связи.
  • Подтвердил, что устройство открыто в общем режиме (AUDCLNT_SHAREMODE_SHARED).
  • Отключен эксклюзивный режим как для воспроизведения, так и для записи в mmsys.cpl для этого устройства (без изменений).
  • Попробовал использовать конечную точку консоли по умолчанию, конечную точку связи по умолчанию и явно выбрал идентификатор устройства.
  • Проверено, что Initialize() и Start() возвращают S_OK — API не дают сбоя, они просто не предоставляют данных.
  • Проверено, что тот же код работает для:
  • Встроенных динамиков,
  • Проводные гарнитуры / USB / 2,4 ГГц.
  • Одна и та же Bluetooth-гарнитура при воспроизведении музыки (YouTube, Spotify) без использования микрофона.
  • Одна и та же Bluetooth-гарнитура воспроизводит звук вызова при использовании другого устройства в качестве микрофона.
Окружающая среда
  • Windows 10/11 (воспроизводится на нескольких машинах).
  • Современные Bluetooth-гарнитуры (например, OnePlus Buds 3, Razer BlackShark V2 HS BT, Anker Soundcore), которые в моем случае представлены как единая конечная точка звука в настройках звука Windows.
Вопросы
  • Есть ли способ захвата звука с Bluetooth-гарнитур во время двунаправленных вызовов использование шлейфа WASAPI или альтернативных API Windows?
  • Если нет, то является ли это ожидаемым поведением для гарнитур Bluetooth и шлейфа WASAPI?
  • Существует ли официальная документация Microsoft об этом ограничении?


Подробнее здесь: https://stackoverflow.com/questions/798 ... bidirectio
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»