OBS Virtual Cam 虚拟麦克风完全指南:把 OBS 声音伪装成系统设备的底层原理一次讲透

发布时间:2026/8/18 18:33:26
OBS Virtual Cam 虚拟麦克风完全指南:把 OBS 声音伪装成系统设备的底层原理一次讲透 OBS Virtual Cam 虚拟麦克风完全指南把 OBS 声音伪装成系统设备的底层原理一次讲透【免费下载链接】obs-virtual-camobs-studio plugin to simulate a directshow webcam项目地址: https://gitcode.com/gh_mirrors/ob/obs-virtual-cam你可能想不到obs-virtual-cam 这个能让 OBS 输出变身成虚拟摄像头和虚拟麦克风的插件音频模块的核心其实只有几百行代码而且它没有注册任何系统虚拟声卡驱动就敢在 Zoom、Teams 里理直气壮地自称麦克风。这背后靠的不是黑魔法而是 Windows 里一个诞生于上个世纪的多媒体框架——DirectShow外加一块共享内存快递站。这篇文章带你从音频链路的最底层扒起把 OBS Virtual Cam 的虚拟麦克风机制彻底看穿。先亮结果它到底解决了什么一句话就能说清它让任何支持 DirectShow 的第三方软件把 OBS 的输出当成一个真实的捕获设备来用。你在 OBS 里加好的噪音抑制、压缩器、EQ从此不是只活在直播间里而是能跟着每一场会议、每一次录屏出门。整条链路可以压成一张总览图OBS 音频处理链(噪音抑制/压缩/EQ) ↓ src/virtual-output 的 virtual_audio() 回调接住 PCM 数据 ↓ 共享内存环形队列(OBSVirtualAudio) ← 写端 ↓ DirectShow 过滤器 CVAudioStream::FillBuffer() ← 读端 ↓ 第三方软件(微信/Zoom/Teams) 的默认麦克风简单说OBS 负责加工DirectShow 过滤器负责伪装成麦克风共享内存负责两个进程之间送货。下面我们沿着这三件事逐个拆。新手必问的三个问题DirectShow 过滤器、共享内存、时间戳到底各管什么问题一软件怎么看见一个不存在的麦克风靠注册表。插件在src/virtual-source/dllmain.cpp的RegisterFilters()里把自己写进了系统的两个设备分类CLSID_AudioInputDeviceCategory音频输入设备和CLSID_VideoInputDeviceCategory视频输入设备。注册完成后控制面板声音设置里就会多出一个名为OBS-Audio的录制设备。关键点这里的过滤器类实现继承的是CSource/CSourceStream源码在src/virtual-source/virtual-audio.cpp的CVAudio和CVAudioStream这俩类来自 DirectShow SDK 的 streams 库——也就是说这个麦克风本质上是一个 DirectShow 源过滤器而不是内核驱动。这也是它不污染系统设备列表、不装驱动的根本原因。问题二OBS 进程和 DirectShow 过滤器是两个程序数据怎么传传指针是行不通的两个进程的内存空间互相看不见。所以插件在src/queue/share_queue.h里用CreateFileMapping/MapViewOfFile建了一块命名共享内存音频通道叫OBSVirtualAudio。OBS 侧是写端src/virtual-output/virtual_output.cpp的virtual_audio()回调DirectShow 侧是读端src/virtual-source/virtual-audio.cpp的FillBuffer()一写一读互不阻塞。读端甚至不需要在 OBS 启动时就存在——会议软件随时打开读端随时shared_queue_open()挂上这块内存。问题三两个进程各自计时怎么保证声音不抖、音画同步这就要提到FillBuffer()里维护的两条时间线obs_start_tsOBS 侧起始时间戳和dshow_start_tsDirectShow 侧起始时间戳。首帧到达时把两者对齐之后每一帧的播放时刻都按dshow_start_ts (timestamp - obs_start_ts) / 100推算。/100是因为 DirectShow 的时间单位是 100 纳秒而 OBS 的时间戳是纳秒。顺带一提src/virtual-source/clock.cpp里get_current_time()用QueryPerformanceCounter高精度计数器换算时间注释写着directshow use 100ns as time unit——单位换算这种事源码里都给你标好了。一个快递站比喻讲透共享内存队列怎么送货不堵车把这条音频通路想象成一个无人快递站OBS 是发货方每个音频帧打包成一件快递贴上时间戳标签放进固定编号的货架队列槽位。DirectShow 过滤器是收货方会议软件每次来催给我一帧它就按货架编号顺序取件。货架区的编号永远在往前滚绕满一圈回到开头继续用——这就是环形队列。但快递站有个聪明的小设计发货方会在 queue_header 里记录delay_frame延迟帧数和write_index当前写到哪个货架。收货方第一次来取货时不直接从最新货架拿而是先回退delay_frame个位置——src/queue/share_queue_read.cpp的share_queue_init_index()干的就是这件事音频模式下还强制max(delay_frame, 3)至少留 3 帧。这等于给系统调度留了一条缓冲带就算某个瞬间发货慢了几毫秒收货方手里还有存粮不会立刻断音。完整链路再走一遍OBS 写端(virtual_audio) → push_audio 写入队列 → write_index 前移 → state 置为 OutputReady ↓ 读端(shared_queue_get_audio) 从 index 位置取帧 → index 前移 → FillBuffer 打上时间戳 → 交给会议软件播放解剖台两段最能体现工程取舍的代码第一刀格式锁定绝不讨价还价src/virtual-source/virtual-audio.hsrc/virtual-source/virtual-audio.cpp// virtual-audio.h —— 对外宣称的出厂规格 #define AUDIO_BUFFER_SIZE 4096 // 单个媒体样本的缓冲字节数 #define SAMPLE_RATE 44100 // 采样率 #define SAMPLE_SIZE 176400 // 每秒字节数 44100 × 2声道 × 2字节// virtual-audio.cpp 的 GetMediaType() —— 自报家门 paf-nChannels 2; // 双声道 paf-nSamplesPerSec SAMPLE_RATE; // 44.1kHz paf-nAvgBytesPerSec SAMPLE_SIZE; // 176400 字节/秒 paf-nBlockAlign 4; // 每帧字节数 声道数 × 位深/8 paf-wBitsPerSample 16; // 16 位深度 paf-wFormatTag WAVE_FORMAT_PCM; // 标准 PCM它决定了什么虚拟麦克风只支持 44.1kHz / 16bit / 立体声 / PCM 这一种格式。GetStreamCaps()里把MinimumSampleFrequency和MaximumSampleFrequency全锁成同一个值等于对系统说别跟我谈判。好处是兼容性极稳、实现极简坏处是想换 48kHz 只能改源码重新编译。与此同时OBS 这头也必须对表src/virtual-output/virtual_output.cpp的virtual_output_start()conv.format AUDIO_FORMAT_16BIT; // 统一转 16bit conv.samples_per_sec 44100; // 统一转 44.1kHz conv.speakers SPEAKERS_STEREO; // 统一转立体声 obs_output_set_audio_conversion(out_data-output, conv);它决定了什么不管 OBS 内部采样率是多少喂进共享内存之前都会被转成 44.1kHz/16bit/立体声。两头对表中间才不会出现格式对不上导致的无声或杂音。第二刀宁可静音绝不给噪音src/virtual-source/virtual-audio.cpp的FillBuffer()// 取不到帧就小睡 5ms 重试最多 20 次 if (!get_sample){ Sleep(5); get_times; } if (get_sample){ size queue.operating_width; start_time dshow_start_ts (timestamp - obs_start_ts) / 100; } else { // 取不到数据把缓冲填零照常输出一帧静音 size pms-GetActualDataLength(); memset(dst, 0, size); }它决定了什么断流时设备不会消失也不会爆音而是输出一段静音。用户听到的是一瞬间没声音而不是刺耳的杂音或设备被系统弹掉。这是它听感稳定的关键设计。同步超时也不是拍脑袋定的SetTimeout()按队列长度动态计算sync_timeout queue.header-queue_length * AUDIO_SIZE * 10000000 / 44100 * 4;队列越长、超时越宽松给系统调度留的余量越大代价是延迟变高——这正是后面那个延迟滑块的物理来源。任务清单从注册设备到会议里出声边做边核对目标一让系统认识 OBS-Audio 这个假麦克风把插件文件解压到 OBS 安装目录后以管理员身份打开 CMD分别注册 32 位和 64 位过滤器regsvr32 C:\Program Files\obs-studio\bin\32bit\obs-virtualsource.dll regsvr32 C:\Program Files\obs-studio\bin\64bit\obs-virtualsource.dll预期现象注册命令返回成功提示打开控制面板 → 声音 → 录制能看到OBS-Audio设备。如果看不到多半是权限不够或 DLL 路径不对重跑一次并确认是管理员 CMD。目标二让 OBS 真正往外发货在 OBS 里找到 Virtual Output 面板勾选 AutoStart自动启动或手动点 Start延迟滑块默认 3 帧即可。预期现象输出处于运行中状态且 OBS 日志出现starting virtual-output字样。这里对应源码virtual_output_start()它会同时创建视频队列和音频队列音频队列长度由video_frame_to_audio_frame()按帧率换算。目标三让会议软件接住这路声音打开微信 / Zoom / Teams 的音频设置把麦克风/输入设备切到OBS-Audio。预期现象说话时能看到输入电平跳动。注意 OBS 必须保持运行因为共享内存的另一端在 OBS 进程里OBS 一关快递站就没人发货了。目标四微调延迟在稳和快之间找平衡延迟滑块范围 0–30 帧源码默认config_set_default_int(config, VirtualOutput, OutDelay, 3)。网络会议建议 1–5 帧本地录屏可以拉到 0。预期现象数字越大越不容易断音但音画延迟越明显改完重启一次输出生效。把坑说在前面边界、局限与权衡这套设计不是没有代价用之前先看清它的脾气只认 44.1kHz。如果某个软件强制请求 48kHz会直接协商失败。遇到采样率不支持的提示去软件设置里强制选 44100Hz。OBS 不能关。它不是独立的虚拟声卡OBS 退出所有正在用 OBS-Audio 的软件瞬间没声——更准确说是输出静音帧。所以不推荐把它当系统常驻麦克风用。有延迟是设计使然。队列缓冲和回退索引换来的是抗抖代价就是延迟。追求极低延迟比如打比赛语音的场景它未必是最优选。只支持 WindowsREADME 明确写了 Windows 7/8/10且依赖 DirectShow 生态。Linux 用户得另找 v4l2 方案Mac 同理。进程冲突shared_queue_create()里有个shared_queue_check()如果检测到同名的共享内存已被占用会拒绝创建——也就是说同一时刻只能有一个 OBS 实例在往某个通道写数据。别忘了OBS 官方从 26.0 起已经内置了虚拟摄像头功能如果你只需要虚拟摄像头而不用虚拟麦克风先想清楚是否真的需要装这个插件。收尾把原理组合拳打一遍回到开头的疑问为什么一个没有驱动、没有声卡的插件敢把自己叫做麦克风答案全在这套组合拳里——DirectShow 过滤器做伪装共享内存队列做搬运双时间戳对表做同步静音兜底做容错。理解了这四招你再遇到无声、卡顿、对不上口型就不是瞎猜而是能顺着数据链路一步步定位。最后送你一句话真正的专业不是设备多贵而是让声音恰到好处地抵达该去的地方。OBS Virtual Cam 用最朴素的几百行代码做到了这件事。【免费下载链接】obs-virtual-camobs-studio plugin to simulate a directshow webcam项目地址: https://gitcode.com/gh_mirrors/ob/obs-virtual-cam创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考