简介:这份资源围绕virtual audio虚拟声卡展开,基于微软官方MSVAD示例实现,面向希望深入理解Windows音频驱动与虚拟设备开发的程序员及音频技术学习者。它解决的是如何在系统层面创建并管理虚拟音频设备、模拟真实硬件行为的问题,涉及驱动模型、音频接口调用与信号处理等核心场景。压缩包共91个文件,以h头文件、cpp源文件为主,辅以makefile、sources等构建配置,以及htm说明、inf安装信息与rc资源脚本,整体约132KB,结构紧凑,便于按模块研读。目前已有828人学习下载。读者可从中获取完整的虚拟声卡实现框架,涵盖DirectSound、WASAPI等接口的对接方式,以及多声道配置、音频路由、设备模拟等实践思路,是理解操作系统内核与音频编程交互的优质学习范例。
1. 虚拟声卡到底在解决什么问题:从一次直播翻车说起
很多人第一次接触 virtual audio 虚拟声卡,不是因为想折腾音频,而是被现实逼的。比如你在做直播或者录屏,想把系统声音、麦克风人声、背景音乐分别控制,结果发现物理声卡只给你一个混好的输出,调大背景音乐人声也被盖住,想单独静音游戏声音又做不到。又或者你在做自动化测试,需要把一段音频“喂”给某个只认麦克风输入的软件,但手头根本没有多余的硬件。这些场景的共同点是:软件把音频输入输出当成了独占资源,而物理声卡的数量和路由能力是固定的。
虚拟声卡就是在这个缝隙里出现的。它本质上是一个内核态的音频驱动,向操作系统注册一个或多个“假的”播放和录音设备。对上层软件来说,它和真实声卡没有区别,可以选它当输出,也可以选它当输入。但对底层来说,它不经过 DAC/ADC,数据直接在内存里流转。这意味着你可以把 A 软件的输出,路由到 B 软件的输入,中间还能插入处理链。适合谁?直播推流、播客录制、语音通话降噪、自动化测试、多轨录音,以及任何需要“把声音从一处搬到另一处”的从业者。
2. 虚拟声卡在系统里是怎么被识别和路由的
2.1 从内核驱动到用户态设备的链路
在 Linux 上,虚拟声卡最常见的实现方式是 ALSA 的snd-aloop模块,或者 PulseAudio 的module-null-sink。snd-aloop会创建一对或多对 loopback 设备,每个设备有一个播放端和一个录音端,播放端写入的数据会直接出现在录音端的读缓冲区里。PulseAudio 的 null sink 则是更高层的抽象,它创建一个虚拟输出,同时提供一个.monitor源,任何从这个输出播放的声音都能被 monitor 源捕获。
Windows 上情况不同,没有内置的 loopback 驱动,需要安装第三方虚拟声卡驱动,比如 VB-Audio 的 Virtual Cable 或者 VoiceMeeter。这类驱动会注册一个 WDM 音频设备,播放端和录音端成对出现,命名上通常带 Cable 或 Virtual 字样。macOS 则依赖 BlackHole 或 Loopback 这类 Audio Server Plugin,它们通过 Core Audio 的 HAL 插件机制注册虚拟设备。
理解这条链路的关键是:虚拟声卡不是“一个软件”,而是“一个驱动 + 一个路由层”。驱动负责让系统看到设备,路由层负责决定谁往哪个设备写、谁从哪个设备读。很多新手翻车,是因为只装了驱动,没配路由,结果声音进了虚拟设备就再也出不来。
2.2 用 snd-aloop 在 Linux 上跑通最小回环
先确认内核模块可用,然后加载。以下命令在大多数主流发行版上通用,但需要 root 权限。
# 加载 ALSA loopback 模块,默认创建 8 对设备 sudo modprobe snd-aloop # 查看是否加载成功,以及生成了哪些设备 aplay -l | grep -i loop arecord -l | grep -i loop加载成功后,你会看到类似card 1: Loopback [Loopback], device 0: Loopback PCM [Loopback PCM]的输出。播放端和录音端通常成对出现,device 0 是播放,device 1 是录音,或者反过来,取决于具体版本。更稳妥的方式是用aplay -L和arecord -L查看 PCM 名称,比如hw:Loopback,0,0和hw:Loopback,0,1。
接下来做一个最小验证:一边播放白噪声,一边录音,看能否录到。
# 终端 A:向 loopback 播放端写入 440Hz 正弦波,持续 5 秒 speaker-test -D hw:Loopback,0,0 -t sine -f 440 -l 1 & # 终端 B:从 loopback 录音端采集 5 秒,保存为 wav arecord -D hw:Loopback,0,1 -f cd -d 5 loopback_test.wav # 播放录到的文件,确认有声音 aplay loopback_test.wav逻辑说明:speaker-test的-D指定播放设备,-t sine生成正弦波,-l 1表示循环一次。arecord的-D指定录音设备,-f cd表示 16bit 44.1kHz 立体声,-d 5表示录 5 秒。如果录到的文件是静音,先检查播放和录音的 device 编号是否配对,再用alsamixer确认 loopback 设备的通道没有被静音。
参数上最常调的是snd-aloop的pcm_substreams和enable。pcm_substreams决定每个设备支持多少个子流,默认 8,做多路路由时可以调大。enable可以指定只启用某几对设备,避免设备列表过长。这些参数写在/etc/modprobe.d/snd-aloop.conf里,格式是options snd-aloop pcm_substreams=4,然后重新加载模块生效。
2.3 PulseAudio 的 null sink 与 monitor 源
如果你不想动内核模块,PulseAudio 提供了更轻量的方案。创建一个 null sink,它会同时生成一个输出和一个 monitor 源。
# 创建一个名为 virtual_out 的虚拟输出 pactl load-module module-null-sink sink_name=virtual_out sink_properties=device.description=Virtual_Out # 查看是否创建成功,以及对应的 monitor 源名称 pactl list short sinks | grep virtual_out pactl list short sources | grep virtual_out创建后,virtual_out会出现在输出设备列表里,virtual_out.monitor会出现在输入设备列表里。任何播放到virtual_out的声音,都能从virtual_out.monitor录到。这个方案的好处是不需要 root,随时加载卸载,适合临时路由。缺点是延迟比 ALSA loopback 略高,通常在 20 到 50 毫秒之间,做实时监听时能感觉到。
要把某个应用的输出强制送到virtual_out,可以用pavucontrol的播放选项卡,在应用运行时切换它的输出设备。命令行方式是用pactl move-sink-input,先找到 sink input 的索引,再移动。
# 列出当前所有播放流 pactl list short sink-inputs # 把索引为 42 的流移动到 virtual_out pactl move-sink-input 42 virtual_out参数说明:sink_name是虚拟输出的唯一标识,后续路由都靠它。sink_properties里的device.description是显示给用户看的名字,可以改成中文或更易识别的名称。如果要做多路隔离,就创建多个 null sink,每个应用送到不同的 sink,再分别从对应的 monitor 录制。
3. 把虚拟声卡接进真实工作流:直播、录制与测试
3.1 直播场景下的三路分离路由
直播最常见的需求是:游戏声音、麦克风人声、背景音乐三者独立控制。物理声卡做不到,虚拟声卡可以。思路是创建两个 null sink,一个给游戏,一个给音乐,麦克风走真实输入,最后用一个混音软件或者ffmpeg把三路合并后推流。
# 创建游戏和音乐两个虚拟输出 pactl load-module module-null-sink sink_name=game_sink sink_properties=device.description=Game pactl load-module module-null-sink sink_name=music_sink sink_properties=device.description=Music # 把游戏进程和音乐播放器的输出分别移动到对应 sink # 假设游戏 sink-input 索引是 10,音乐是 11 pactl move-sink-input 10 game_sink pactl move-sink-input 11 music_sink # 用 ffmpeg 混合三路:游戏 monitor、音乐 monitor、真实麦克风 ffmpeg -f pulse -i game_sink.monitor \ -f pulse -i music_sink.monitor \ -f pulse -i default \ -filter_complex "[0:a][1:a][2:a]amix=inputs=3:duration=longest[aout]" \ -map "[aout]" -f pulse default逻辑说明:-f pulse指定 PulseAudio 输入,game_sink.monitor和music_sink.monitor分别捕获两个虚拟输出的声音,default是真实麦克风。amix滤波器把三路混合成一路,duration=longest表示以最长的输入为准。最后输出到default,也就是真实扬声器或推流软件的输入。
参数上,amix的weights可以单独调每一路的音量,比如weights=1 0.5 2表示游戏原音量、音乐减半、麦克风加倍。如果要做侧链压缩,让音乐在说话时自动降低,可以把amix换成sidechaincompress,但配置会复杂不少,建议先用amix跑通再进阶。
3.2 用虚拟声卡做自动化音频测试
自动化测试里经常需要模拟麦克风输入,比如测试语音识别服务。物理上不可能每次都用真人说话,虚拟声卡可以把预录的音频文件“伪装”成麦克风输入。
# 创建一个虚拟麦克风 sink pactl load-module module-null-sink sink_name=fake_mic sink_properties=device.description=Fake_Mic # 把预录的 wav 循环播放到 fake_mic ffmpeg -stream_loop -1 -i test_audio.wav -f pulse fake_mic # 被测程序选择 fake_mic.monitor 作为输入设备逻辑说明:-stream_loop -1让音频无限循环,-f pulse fake_mic把解码后的音频送到虚拟输出。被测程序从fake_mic.monitor读取,就相当于从麦克风读取。这样可以在无人值守的情况下反复跑测试用例。
参数上,-re可以控制按原始速度播放,避免 ffmpeg 以最快速度灌数据导致被测程序缓冲区溢出。如果测试需要模拟噪声,可以在 ffmpeg 里加anoisesrc源,和语音混合后再输出。延迟方面,PulseAudio 的默认缓冲在 200 毫秒左右,对大多数语音识别测试够用,但如果被测程序对实时性要求高,需要调小module-null-sink的latency_msec参数。
3.3 Windows 上的 Virtual Cable 配置要点
Windows 没有pactl这种命令行工具,配置主要靠系统声音设置和软件自带的面板。安装 Virtual Cable 后,系统会多出一个播放设备和一个录音设备,通常叫CABLE Input和CABLE Output。要让某个软件的声音进入虚拟声卡,就在该软件的音频设置里把输出选成CABLE Input。要让另一个软件从虚拟声卡读取,就把它的输入选成CABLE Output。
关键坑在于采样率。Virtual Cable 默认可能是 44100Hz,而某些软件强制 48000Hz,两边不一致会导致声音变调或者直接没声音。解决方法是右键系统托盘的音量图标,进入声音设置,找到CABLE Input和CABLE Output,在高级选项卡里把格式统一成2 通道 16 位 48000Hz。如果软件有自己的音频引擎,比如某些直播软件,还要在软件内部再确认一遍采样率。
另一个坑是独占模式。Windows 的音频设备默认允许应用程序独占,一旦某个软件以独占模式占用了CABLE Input,其他软件就写不进去。在声音设置的设备属性里,把“允许应用程序独占控制该设备”取消勾选,可以避免这个问题。代价是延迟可能略微增加,但稳定性更好。
4. 虚拟声卡配置里最容易翻车的五个地方
4.1 声音进了虚拟设备就出不来
现象:把播放器输出切到虚拟声卡后,扬声器立刻没声音了,录音端也录不到东西。
原因:虚拟声卡是一个闭环,播放端写入的数据只会出现在录音端,不会自动转发到物理扬声器。很多人以为切过去就能同时听到,实际上需要额外做一路监听。
解决:在 PulseAudio 里,用module-loopback把虚拟输出的 monitor 接到真实扬声器。命令是pactl load-module module-loopback source=virtual_out.monitor sink=alsa_output.pci-0000_00_1f.3.analog-stereo,其中 sink 名称用pactl list short sinks查到的真实输出替换。Windows 上则在 Virtual Cable 的面板里启用监听,或者用 VoiceMeeter 做路由。
4.2 延迟大到无法实时监听
现象:说话后要等半秒才能从耳机里听到自己的声音,完全没法跟唱或主持。
原因:虚拟声卡本身不引入大延迟,但 PulseAudio 的默认缓冲和重采样会累积延迟。如果中间还经过module-loopback,每经过一层就多一次缓冲。
解决:把module-null-sink的latency_msec调到 10 以下,module-loopback的latency_msec也调小。在/etc/pulse/daemon.conf里把default-fragments和default-fragment-size-msec调优,比如default-fragments=4、default-fragment-size-msec=5。代价是 CPU 占用略升,极端情况下可能爆音,需要根据机器性能找平衡点。
4.3 采样率不匹配导致变调或杂音
现象:录出来的声音比原声慢半拍或者快半拍,或者夹杂噼啪声。
原因:播放端和录音端的采样率不一致,或者虚拟设备与真实设备时钟不同步。PulseAudio 会自动重采样,但重采样算法在极端比例下会引入失真。
解决:统一所有虚拟设备和真实设备的采样率。用pactl list short sinks查看每个 sink 的Sample Specification,把不一致的通过pactl set-sink-formats或者配置文件改成一致。ALSA loopback 则要在asound.conf里显式指定rate 48000。如果时钟不同步,考虑用module-combine-sink做时钟同步,而不是简单 loopback。
4.4 重启后虚拟设备消失
现象:配置好之后一切正常,重启电脑发现虚拟声卡不见了,软件里也选不到。
原因:pactl load-module是运行时加载,重启后 PulseAudio 重新启动,之前加载的模块不会自动恢复。snd-aloop如果没写进/etc/modules或者 modprobe 配置,也不会自动加载。
解决:把pactl load-module命令写进~/.config/pulse/default.pa或者/etc/pulse/default.pa,放在### Load audio drivers statically之后。snd-aloop则写进/etc/modules-load.d/snd-aloop.conf,参数写进/etc/modprobe.d/snd-aloop.conf。Windows 上 Virtual Cable 是驱动级安装,重启不会消失,但如果用了 VoiceMeeter 的虚拟设备,要确认它的服务是自动启动。
4.5 多路路由时互相串音
现象:明明把游戏和音乐分到了两个虚拟声卡,但录游戏的时候能听到音乐。
原因:某些软件不遵守系统默认输出,或者 PulseAudio 的module-stream-restore把之前的路由规则恢复了。也可能是创建虚拟设备时用了同一个sink_name,导致实际是同一个设备。
解决:先确认每个虚拟设备的sink_name唯一,用pactl list short sinks核对。然后关闭module-stream-restore,或者用pactl unload-module module-stream-restore临时禁用,避免它把应用强制拉回旧设备。如果软件本身有“跟随系统默认”的选项,把它改成“指定设备”。最后用pactl list short sink-inputs确认每个流的实际归属,发现串音就手动move-sink-input纠正。
5. 用 ffmpeg 做虚拟声卡的实时验证与录制
配置完虚拟声卡,怎么确认它真的在工作?最直接的办法是用 ffmpeg 同时做播放和录制,并且把波形可视化。下面这个命令把一段测试音频送到虚拟声卡,同时从虚拟声卡录回来,并生成频谱图。
# 生成 10 秒测试音频:1kHz 正弦波 + 白噪声 ffmpeg -f lavfi -i "sine=frequency=1000:duration=10" \ -f lavfi -i "anoisesrc=d=10:c=pink:r=48000" \ -filter_complex "[0:a][1:a]amix=inputs=2:duration=shortest[aout]" \ -map "[aout]" -ar 48000 -ac 2 test_input.wav # 播放到虚拟声卡,同时从 monitor 录制,并输出频谱图 ffmpeg -re -i test_input.wav -f pulse virtual_out \ -f pulse -i virtual_out.monitor \ -filter_complex "[1:a]showspectrumpic=s=800x400:legend=1[spec]" \ -map "[spec]" -frames:v 1 spectrum.png \ -map 1:a -f null -逻辑说明:第一条命令用lavfi生成正弦波和粉红噪声,混合成 10 秒立体声文件。第二条命令用-re按实时速度播放到virtual_out,同时从virtual_out.monitor读取,用showspectrumpic生成频谱图,-frames:v 1表示只输出一帧图像。-map 1:a -f null -表示把录到的音频丢弃,只做验证。
参数上,-re是关键,不加的话 ffmpeg 会以最快速度灌完,录到的可能不完整。showspectrumpic的s指定图像尺寸,legend=1显示频率图例。如果频谱图里能看到 1kHz 的亮线和粉红噪声的宽带能量,说明虚拟声卡的回环链路是通的。如果只有一条平线,检查virtual_out和virtual_out.monitor是否配对,以及 PulseAudio 是否把播放流正确路由到了virtual_out。
这个验证方法的好处是不依赖耳朵,频谱图能直观看出频率响应和噪声底。我一般会在每次改完路由配置后跑一遍,确认没有意外的高频衰减或直流偏移。另外,如果要做长时间稳定性测试,把-frames:v 1去掉,改成-f null -持续录制,同时用top观察 ffmpeg 的 CPU 占用,虚拟声卡本身开销很小,瓶颈通常在重采样和滤波器上。
最后一个习惯:所有pactl load-module命令都记在一个脚本里,改配置前先pactl unload-module对应模块,再重新加载。这样即使把音频路由搞乱了,也能一键回到干净状态。虚拟声卡这东西,玄学的地方不多,大部分问题都能通过看设备列表和频谱图定位。希望帮到你。
本文还有配套的精品资源,点击获取