参考文档
COM Coding Practices
Audio File Format Specifications
Core Audio APIs
Loopback Recording
#include <iostream>
#include <fstream>
#include <vector>
#include <mmdeviceapi.h>
#include <combaseapi.h>
#include <atlbase.h>
#include <Functiondiscoverykeys_devpkey.h>
#include <Audioclient.h>
#include <Audiopolicy.h>
// 利用RAII手法,自动调用 CoUninitialize
class CoInitializeGuard {
public:
CoInitializeGuard()
{
_hr = CoInitializeEx(nullptr, COINIT::COINIT_MULTITHREADED);
}
~CoInitializeGuard()
{
if (_hr == S_OK || _hr == S_FALSE) {
CoUninitialize();
}
}
HRESULT result() const { return _hr; }
private:
HRESULT _hr;
};
constexpr inline void exit_on_failed(HRESULT hr);
void printEndpoints(CComPtr<IMMDeviceCollection> pColletion);
std::string wchars_to_mbs(const wchar_t* s);
int main()
{
HRESULT hr{};
CoInitializeGuard coInitializeGuard;
exit_on_failed(coInitializeGuard.result());
// COM 对象都用 CComPtr 包装,会自动调用 Release
// COM 接口分配的堆变量用 CComHeapPtr 包装,会自动调用 CoTaskMemFree
CComPtr<IMMDeviceEnumerator> pEnumerator;
hr = pEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
exit_on_failed(hr);
// 打印所有可用的音频设备
//CComPtr<IMMDeviceCollection> pColletion;
//hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pColletion);
//exit_on_failed(hr);
//printEndpoints(pColletion);
// 使用默认的 Audio Endpoint,eRender 表示音频播放设备,而不是录音设备
CComPtr<IMMDevice> pEndpoint;
hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pEndpoint);
exit_on_failed(hr);
// 打印出播放设备的名字,可能包含中文
CComPtr<IPropertyStore> pProps;
hr = pEndpoint->OpenPropertyStore(STGM_READ, &pProps);
exit_on_failed(hr);
PROPVARIANT varName;
PropVariantInit(&varName);
hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName);
exit_on_failed(hr);
std::cout << "select audio endpoint: " << wchars_to_mbs(varName.pwszVal) << std::endl;
PropVariantClear(&varName);
// 由 IMMDevice 对象 得到 IAudioClient 对象
CComPtr<IAudioClient> pAudioClient;
hr = pEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&pAudioClient);
exit_on_failed(hr);
// 获得音频播放设备格式信息
CComHeapPtr<WAVEFORMATEX> pDeviceFormat;
pAudioClient->GetMixFormat(&pDeviceFormat);
constexpr int REFTIMES_PER_SEC = 10000000; // 1 reference_time = 100ns
constexpr int REFTIMES_PER_MILLISEC = 10000;
// 初始化 IAudioClient 对象
const REFERENCE_TIME hnsRequestedDuration = 2 * REFTIMES_PER_SEC; // 1s
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, hnsRequestedDuration, 0, pDeviceFormat, nullptr);
exit_on_failed(hr);
// 获得缓冲区大小
UINT32 bufferFrameCount{};
hr = pAudioClient->GetBufferSize(&bufferFrameCount);
exit_on_failed(hr);
// 由 IAudioClient 对象 得到 IAudioCaptureClient 对象,也就是将音频播放设备视为录音设备
CComPtr<IAudioCaptureClient> pCaptureClient;
hr = pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pCaptureClient);
exit_on_failed(hr);
// 开始录音
hr = pAudioClient->Start();
exit_on_failed(hr);
const REFERENCE_TIME hnsActualDuration = (long long)REFTIMES_PER_SEC * bufferFra