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
- Recording from a wired headset
- Recording from 2.4 GHz Headset
- Recording from a laptop's built in speaker
- 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
- Is there any way to capture audio from Bluetooth headsets during bidirectional calls using WASAPI loopback or alternative Windows APIs?
- If not then is this the expected behavior for Bluetooth headsets and WASAPI loopback?
- Is there official Microsoft documentation about this limitation?
-
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).Mike Petrichenko– Mike Petrichenko2025年12月04日 18:49:25 +00:00Commented 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.fahdIm– fahdIm2025年12月05日 07:00:54 +00:00Commented 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.Mike Petrichenko– Mike Petrichenko2025年12月05日 10:14:31 +00:00Commented Dec 5, 2025 at 10:14