1

I want to record the system audio using the WASAPI Loopback recording ( https://learn.microsoft.com/en-us/windows/win32/coreaudio/loopback-recording ).

I built a small application that enumerates the available devices and the sessions active on them. The application then records a few seconds of audio and saves into a wav file.

This seems to work well but the issue occurs with bluetooth headsets in a video call.

What Works

  1. Recording from a wired headset
  2. Recording from 2.4 GHz Headset
  3. Recording from a laptop's built in speaker
  4. Recording from a Bluetooth headset when only the speaker is being used.

What Fails

When a Bluetooth headset is used as both microphone and speakers in a video call (e.g. Chrome/WebRTC call), loopback capture on that device produces zero bytes of audio, even though the session enumerator shows that device has an active session.

What I see in my logs

During a test call (Chrome in a meeting), my render device enumerator prints something like this:

=== 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)

Then recording starts for the active device:

=== 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

So:

  • I see the Bluetooth headset device.
  • I see an active session on it (Chrome, via IAudioSessionManager2 / IAudioSessionControl2).
  • I successfully initialize loopback capture on that exact endpoint.
  • But no data ever arrives in the loopback stream (0 bytes for the full duration).
  • IAudioCaptureClient::GetNextPacketSize consistently returns 0, and the sample-ready event is never signaled.

If I repeat the same test using:

  • A wired headset, or
  • A 2.4 GHz USB headset, or
  • The same Bluetooth headset but playing music/video (without the microphone being used by any application),

then the loopback capture works and I get valid WAV data.

Code: Device Enumeration and Session Detection

Here’s how I enumerate devices and detect active sessions:

// Enumerate render devices
wil::com_ptr<IMMDeviceEnumerator> deviceEnum;
CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
 IID_PPV_ARGS(deviceEnum.put()));
wil::com_ptr<IMMDeviceCollection> 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<IMMDevice> 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<IAudioSessionManager2> sessionMgr;
 device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,
 nullptr, (void**)sessionMgr.put());
 wil::com_ptr<IAudioSessionEnumerator> 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<IAudioSessionControl> 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());
 }
}

How I open loopback capture

The capture code is standard WASAPI loopback:

  • Get the IMMDevice by ID using IMMDeviceEnumerator::GetDevice.

  • IMMDevice::Activate to get IAudioClient.

  • IAudioClient::GetMixFormat to get the device mix format.

  • IAudioClient::Initialize in shared mode with loopback flags:

    hr = audioClient->Initialize(
     AUDCLNT_SHAREMODE_SHARED,
     AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
     hnsBufferDuration,
     0,
     mixFormat,
     nullptr);
    

My capture loop:

// Set up event callback
HANDLE sampleReadyEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
audioClient->SetEventHandle(sampleReadyEvent);
// Get capture client
wil::com_ptr<IAudioCaptureClient> 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();

Things I have already tried

  • Confirmed that the session is visible and active via IAudioSessionManager2 on the same device I open for loopback.
  • Confirmed the device is opened in shared mode (AUDCLNT_SHAREMODE_SHARED).
  • Disabled exclusive mode for both playback and recording in mmsys.cpl for that device (no change).
  • Tried using the default console endpoint, the default communications endpoint, and selecting explicitly by device ID.
  • Verified that Initialize() and Start() both return S_OK - the APIs don't fail, they just provide no data
  • Verified that the same code works for:
  • Built-in speakers,
  • Wired / USB / 2.4 GHz headsets,
  • The same Bluetooth headset when playing music (YouTube, Spotify) without microphone usage.
  • The same Bluetooth headset playing call audio while using a different device for the microphone

Environment

  • Windows 10 / 11 (reproduced on multiple machines).
  • Modern Bluetooth headsets (e.g., OnePlus Buds 3, Razer BlackShark V2 HS BT, Anker Soundcore) that, in my case, present as a single audio endpoint in Windows sound settings.

Questions

  1. Is there any way to capture audio from Bluetooth headsets during bidirectional calls using WASAPI loopback or alternative Windows APIs?
  2. If not then is this the expected behavior for Bluetooth headsets and WASAPI loopback?
  3. Is there official Microsoft documentation about this limitation?
asked Dec 4, 2025 at 14:24
3
  • I think the problem is in HFP profile and driver used for video calls. For audio playback the A2DP used, instead for HFP. Try to disable HFP profile for your device can check if it is available for video calling (microphone should not be available). Commented Dec 4, 2025 at 18:49
  • @MikePetrichenko It could be the case, the only way I saw of disabling the HFP profile was stopping the Bluetooth Audio Gateway Service from services.msc and that did indeed disable the microphone of my bluetooth headset. But I'm still wondering if this is the expected behavior or something wasapi loopback is supposed to be able to handle given that the stream/session is shown as running in shared mode. And also that Microsoft don't document anything regarding this limitation. And if there are any alternatives. Commented Dec 5, 2025 at 7:00
  • I do not think there is alternative. HFP works in a little bit different way than other audio drivers. Commented Dec 5, 2025 at 10:14

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.