如何在Windows 10上用Rust/C++创建虚拟音频输入设备模拟麦克风
在Windows 10上创建可写入的虚拟麦克风(Rust/C++实现)
核心思路澄清
WASAPI是正确的技术路径,但Windows Core Audio API本身无法直接创建虚拟音频设备——虚拟设备需要驱动级支持。最实用的方案是:使用现成的虚拟音频驱动(如VB-Cable)作为中转,通过WASAPI将自定义音频流写入该驱动的播放端,驱动会自动将其映射为可被其他应用捕获的麦克风输入。如果需要完全自定义驱动,需使用Windows Driver Kit (WDK) 开发Virtual Audio Device (VAD),但门槛较高。
C++实现方案
步骤1:准备虚拟音频设备
安装VB-Cable(或类似虚拟音频驱动),它会创建一对配对设备:CABLE Output(播放端)和CABLE Input(捕获端,即虚拟麦克风)。
步骤2:用WASAPI写入音频流
以下是核心代码片段,实现从PCM文件读取数据并写入虚拟播放设备:
#include <windows.h> #include <mmdeviceapi.h> #include <audioclient.h> #include <functiondiscoverykeys_devpkey.h> #include <vector> #include <fstream> #pragma comment(lib, "ole32.lib") #pragma comment(lib, "winmm.lib") int main() { // 初始化COM环境 CoInitializeEx(nullptr, COINIT_MULTITHREADED); // 获取音频设备枚举器 IMMDeviceEnumerator* pEnumerator = nullptr; CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator); // 枚举激活的播放设备,定位VB-Cable Output IMMDeviceCollection* pDevices = nullptr; pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices); UINT deviceCount = 0; pDevices->GetCount(&deviceCount); IMMDevice* pTargetDevice = nullptr; for (UINT i = 0; i < deviceCount; i++) { IMMDevice* pDevice = nullptr; pDevices->Item(i, &pDevice); IPropertyStore* pProps = nullptr; pDevice->OpenPropertyStore(STGM_READ, &pProps); PROPVARIANT varName; PropVariantInit(&varName); pProps->GetValue(PKEY_Device_FriendlyName, &varName); if (wcscmp(varName.pwszVal, L"CABLE Output") == 0) { pTargetDevice = pDevice; break; } PropVariantClear(&varName); pProps->Release(); pDevice->Release(); } // 初始化AudioClient IAudioClient* pAudioClient = nullptr; pTargetDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&pAudioClient); WAVEFORMATEXTENSIBLE* pMixFormat = nullptr; pAudioClient->GetMixFormat((WAVEFORMATEX**)&pMixFormat); // 调整格式为16位、44100Hz、单声道(可按需修改) pMixFormat->Format.wBitsPerSample = 16; pMixFormat->Format.nSamplesPerSec = 44100; pMixFormat->Format.nChannels = 1; pMixFormat->Format.nBlockAlign = pMixFormat->Format.nChannels * pMixFormat->Format.wBitsPerSample / 8; pMixFormat->Format.nAvgBytesPerSec = pMixFormat->Format.nSamplesPerSec * pMixFormat->Format.nBlockAlign; // 初始化共享模式,设置100ms延迟 REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC / 10; pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, hnsRequestedDuration, 0, (WAVEFORMATEX*)pMixFormat, nullptr); // 获取RenderClient用于写入数据 IAudioRenderClient* pRenderClient = nullptr; pAudioClient->GetService(__uuidof(IAudioRenderClient), (void**)&pRenderClient); // 获取缓冲区帧数量 UINT32 bufferFrameCount = 0; pAudioClient->GetBufferSize(&bufferFrameCount); // 启动音频流 pAudioClient->Start(); // 读取PCM文件并循环写入缓冲区 std::ifstream pcmFile("audio.pcm", std::ios::binary); std::vector<char> buffer(bufferFrameCount * pMixFormat->Format.nBlockAlign); while (!pcmFile.eof()) { pcmFile.read(buffer.data(), buffer.size()); UINT32 bytesRead = pcmFile.gcount(); if (bytesRead == 0) break; UINT32 framesToWrite = bytesRead / pMixFormat->Format.nBlockAlign; BYTE* pData = nullptr; pRenderClient->GetBuffer(framesToWrite, &pData); memcpy(pData, buffer.data(), bytesRead); pRenderClient->ReleaseBuffer(framesToWrite, 0); } // 清理资源 pAudioClient->Stop(); pRenderClient->Release(); pAudioClient->Release(); pTargetDevice->Release(); pDevices->Release(); pEnumerator->Release(); CoUninitialize(); return 0; }
注意事项
- 确保PCM文件格式与代码中设置的采样率、位深、声道数完全匹配,否则会出现杂音或无声。
- 若需低延迟,可切换为
AUDCLNT_SHAREMODE_EXCLUSIVE独占模式,但会占用设备导致其他应用无法使用。
Rust实现方案
步骤1:依赖准备
使用cpal(封装WASAPI的跨平台音频库)和hound(WAV文件读取库),在Cargo.toml中添加:
[dependencies] cpal = "0.15.0" hound = "3.5.0"
步骤2:核心代码
以下示例读取WAV文件并写入VB-Cable输出设备:
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use hound::{WavReader, SampleFormat}; fn main() { // 获取Windows默认音频主机 let host = cpal::default_host(); // 枚举播放设备,定位VB-Cable Output let device = host .output_devices() .unwrap() .find(|d| d.name().unwrap_or_default() == "CABLE Output") .expect("未找到VB-Cable Output设备"); // 获取设备默认输出配置(可手动指定采样率、声道数) let config = device.default_output_config().unwrap(); // 手动配置示例: // let config = cpal::StreamConfig { // channels: 1, // sample_rate: cpal::SampleRate(44100), // buffer_size: cpal::BufferSize::Default, // }; // 读取WAV文件并验证格式匹配 let reader = WavReader::open("audio.wav").unwrap(); let spec = reader.spec(); assert_eq!(spec.channels, config.channels() as u16); assert_eq!(spec.sample_rate, config.sample_rate().0); // 生成音频流写入逻辑 let mut samples = reader.samples::<i16>(); let write_data = move |data: &mut [i16], _: &cpal::OutputCallbackInfo| { for sample in data { *sample = samples.next().unwrap().unwrap_or(0); } }; // 根据采样格式创建并启动流 let stream = match config.sample_format() { cpal::SampleFormat::I16 => device.build_output_stream( &config.into(), write_data, |err| eprintln!("音频流错误: {}", err), None, ).unwrap(), _ => panic!("不支持的采样格式"), }; stream.play().unwrap(); // 阻塞等待播放完成(可根据实际需求调整) std::thread::sleep(std::time::Duration::from_secs(spec.duration().unwrap().as_secs())); }
注意事项
cpal会自动处理WASAPI的缓冲区管理,无需手动操作底层API。- 若需从自定义生成的采样数据写入,可替换
hound的读取逻辑为自己的采样生成代码。
自定义驱动的进阶方案
若需完全自主创建虚拟设备(无需依赖第三方驱动),需使用WDK开发Virtual Audio Device (VAD):
- 参考微软官方的
Virtual Audio Device示例(包含在WDK安装包中)。 - 实现音频捕获设备的驱动逻辑,接收用户模式程序的音频数据并暴露为系统麦克风。
- 注意:驱动需要数字签名,否则Windows 10会阻止加载(测试时可启用测试签名模式)。
内容的提问来源于stack exchange,提问作者Ahmed Tounsi
相关产品推荐
相关产品推荐

