C++如何读取系统音频输入设备列表?(PortAudio或WASAPI枚举)

1次阅读

portaudio 列出所有音频输入设备需先调用 pa_initialize() 确保成功,再用 pa_getdevicecount() 获取总数,循环调用 pa_getdeviceinfo(i) 并检查 info->maxinputchannels > 0 才视为有效输入设备,设备名取 info->name。

C++如何读取系统音频输入设备列表?(PortAudio或WASAPI枚举)

PortAudio 怎么列出所有音频输入设备

PortAudio 的 Pa_GetDeviceCount()Pa_GetDeviceInfo() 是唯一可靠入口,别想绕过它查系统注册表或调用底层 API —— 那样既跨平台失效,又容易漏掉虚拟设备(比如 VB-Cable、Voicemeeter 虚拟线)。

常见错误是只调用一次 Pa_Initialize() 就开始枚举,但实际必须确保初始化成功且未被提前终止;还有人忽略 PaDeviceInfo::maxInputChannels > 0 这个关键判断,把纯输出设备也当输入设备列出来。

  • 先调用 Pa_Initialize(),检查返回值是否为 paNoError
  • Pa_GetDeviceCount() 获取总数,再循环调用 Pa_GetDeviceInfo(i)
  • 对每个设备指针,检查 info->maxInputChannels > 0 才算有效输入设备
  • 设备名取 info->name,别依赖 info->hostApi 做分类——WASAPI 和 DirectSound 设备可能共享同一 host API ID
int deviceCount = Pa_GetDeviceCount(); for (int i = 0; i < deviceCount; ++i) {     const PaDeviceInfo* info = Pa_GetDeviceInfo(i);     if (info && info->maxInputChannels > 0) {         printf("Input device %d: %sn", i, info->name);     } }

WASAPI 枚举输入设备时为什么 GetDefaultAudioEndpoint 失败

IMMDeviceEnumerator::GetDefaultAudioEndpoint() 默认只返回“默认通信设备”或“默认播放/录制设备”,不是全量列表。想拿到全部输入设备,必须用 EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED),否则插着的麦克风都可能被过滤掉。

另一个高频坑是没正确设置 COM 初始化模式:WASAPI 必须在单线程公寓(STA)下初始化,CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) 缺一不可;用 COINIT_MULTITHREADED 会导致后续接口调用静默失败或返回 E_NOINTERFACE

立即学习C++免费学习笔记(深入)”;

  • 必须先调用 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
  • enumerator->EnumAudioEndpoints(eCapture, ...),别用 GetDefaultAudioEndpoint()
  • 遍历 IMMDeviceCollection 时,对每个 IMMDevice 调用 OpenPropertyStore(STGM_READ) 读取 PKEY_Device_FriendlyName
  • 注意释放 IMMDeviceIMMDeviceCollection,否则设备句柄泄漏

PortAudio 和 WASAPI 枚举结果不一致怎么办

PortAudio 在 windows 上默认走 WASAPI 后端,但它的设备索引和 WASAPI 的 IMMDevice GUID 完全无关——PortAudio 内部做了抽象映射,且会跳过某些被标记为“禁用”或“未连接”的设备(哪怕物理上插着),而 WASAPI 的 DEVICE_STATE_UNPLUGGED 可能仍把它列出来。

更麻烦的是:PortAudio 的 Pa_GetDeviceInfo() 返回的采样率范围(defaultSampleRate)是 host 推荐值,不是设备真实能力;WASAPI 则可通过 IAudioClient::IsFormatSupported() 实际探测,但 PortAudio 不暴露这层。

  • 不要假设 PortAudio 索引 i 对应 WASAPI 第 i 个设备——它们没有对应关系
  • 若需精确匹配,只能靠设备名字符串粗略比对(info->name vs PKEY_Device_FriendlyName),但注意空格、括号、驱动附加后缀(如 “(VB-Audio Voicemeeter VAIO)”)
  • PortAudio 不报告设备是否支持独占模式,WASAPI 可通过 IAudioClient::GetSharedModeEnginePeriod() 判断,这点常被忽略

为什么枚举出来的设备名中文乱码或显示为英文

PortAudio 的 PaDeviceInfo::name 是 UTF-8 编码的 C 字符串,但 Windows 控制台默认用本地 ANSI(如 GBK)解码,直接 printf 会乱码;WASAPI 的 PKEY_Device_FriendlyName 是 UTF-16,用 wprintf 或转成 UTF-8 才能正常显示。

最容易被忽略的是:即使你用了 SetConsoleOutputCP(CP_UTF8),如果程序启动时控制台编码已被父进程锁定(比如从 VS 调试器启动),该设置可能无效。

  • PortAudio 设备名:用 MultiByteToWideChar(CP_UTF8, ...) 转宽字符再输出,或确保终端支持 UTF-8
  • WASAPI 设备名:用 wprintf(L"%s", friendlyName),别用 printf("%S", ...) —— 后者行为不可靠
  • 发布时别依赖控制台编码设置,优先写入日志文件并用 UTF-8 bom 标记

设备名本身不含路径、权限或实时状态信息,它只是个静态标识符。真要区分同名设备(比如两个 USB 麦克风),得靠底层硬件 ID(WASAPI 的 PKEY_Device_InstanceId)或 PortAudio 的 hostApi + PaHostApiInfo::type 组合,但这部分 PortAudio 不对外暴露,只能自己 patch 或换 WASAPI。

text=ZqhQzanResources