A Camera Without OBS

Blog post #68


Yesterday Dockcam needed OBS. The phone streamed to a page on the laptop, OBS captured that page and turned it into a virtual camera, and Teams or HeyGen read from OBS. It worked, but “install OBS and add a browser source with these six settings” is not a product. Today the middle step is gone. The phone streams to the app, and the app is the camera.

That screenshot is the first time I picked “Dockcam” from a camera list in a real program. I did not touch OBS.

What shipped

A virtual camera for Windows 11. Windows has had an official way to do this since 2022: a program can register a software camera, and the operating system loads a component you wrote whenever some application opens that camera. No kernel driver, no driver signing. The component is a small C++ library, about a thousand lines, that reads video frames from the Dockcam app and hands them to Windows at thirty frames per second. It offers 1080p, 720p, 540p and 360p and scales to whatever the calling program asks for. When the phone is not streaming it shows the Dockcam logo instead of a black box.

A helper program that registers the camera when the app starts and removes it when the app quits. Registration needs administrator rights once, because the component has to live in Program Files and be listed in the machine-wide registry. The installer asks for that, and if you cancel there is a button in the app for later.

A new “Camera” card in the app. It shows whether the camera is activated, whether a program is currently reading from it, and lets you switch the output back to OBS if you want the old setup.

A test tool that reads from the camera the same way Teams does, saves a frame to disk, and reports the frame rate. Claude used it to verify everything before I was at the computer.

What’s working

The whole chain. iPhone in Chrome, WiFi, the Dockcam app, the Windows camera service, HeyGen, and a Messenger call in Chrome. Thirty frames per second measured, correct colours, correct scaling from the phone’s 720p to the 1080p HeyGen asked for.

The Windows camera service runs as a system account, not as me. That decided a lot of the design. It cannot read files in my user folder, so the component lives in Program Files. It cannot see my part of the registry, so registration is machine-wide. And the frames travel over a named pipe, because that is the one channel where the default permissions already let a system service read from a user program.

What’s unclear or broken

Updating the camera component is awkward. Once any program has used the camera, Windows keeps the component loaded, and you cannot overwrite a loaded file. The registration step now stops the camera service, swaps the file and starts it again. That is fine for an installer. It is something to remember when I build auto-update.

For a while the camera worked in HeyGen and then vanished from a Messenger call in Chrome. It took an hour to understand. Claude Code runs inside the Claude desktop app, which is a packaged Windows app with its own virtualised copy of my user profile. Every Dockcam instance Claude started lived in that copy: its own settings, its own room code, and a camera registration my Chrome could not see. The instance I started myself was fine. The rule now is that Claude does not launch the installed app, and if it has to, it goes through a scheduled task that runs outside the package.

The reference project I read warns that Teams sometimes shows a virtual camera in the preview but does not send it to the other side of the call. I have tested HeyGen, not a Teams call with a second person. That is next.

Decisions made

No native Node addon. The plan said the app would talk to the camera through a compiled addon inside Electron. Claude proposed a separate helper executable instead: the app starts it, and when the app dies its input closes and the camera disappears with it. No build step tied to the Electron version, no rebuild on every update. I did not have an opinion until I saw how much simpler it was.

Hand-written COM. The two reference implementations I found both pull in Microsoft’s helper libraries through NuGet. The Dockcam component uses the Windows SDK and nothing else. It compiles with one command and has no runtime dependencies, which matters when the file is loaded into a system service.

Tooling & process

This was one session, most of it while I was away from the computer. Claude installed the Visual Studio build tools, read two open source virtual camera projects for reference, wrote the component, and tested it without administrator rights by loading it directly into a test program instead of through Windows. When I came back the only thing waiting was the permission prompt.

Two things went wrong that I want to remember. The first: Claude’s test producer and the app both tried to register the camera at the same time, and the second one, when it failed, took the first one’s camera down with it. The fix was a few lines, but it is the kind of bug you only find by running two things at once. The second: the first two attempts to test the real registration stalled on the UAC prompt because nobody was at the machine to click it. Autonomous does not mean unattended when Windows wants a human.

Total: about four hours from “the plan says N-API addon” to a camera in HeyGen’s list.


— Stefan