iddSampleDriver woes - trying to connect the dots

Like others before me, I’m trying to create a virtual display driver using the Idd(Cx)SampleApp from the WDK for Windows 10 1903. I’ve written for Windows since the late '80s, but I’m new to the DDK/WDK. I’ve been working on this for several weeks, and I’ve read everything relevant that I can find online.

Background:

In a response to a older post from someone attempting a similar goal, Marcel Ruedinger (Datronisoft) stated:

Marcel Ruedinger
Two types of WDDM display drivers can reasonably be written by regular driver developers:

  • WDDM DOD Display Only Drivers (e.g. KMDOD).
  • WDDM IddCx Indirect Display Drivers.

The downside of both: Currently they both need real hardware.

This was a non-starter for me, because I’m trying to create a virtual display (adapter/monitor) with no hardware backing (1).


But then I read this in another post:

Marcel Ruedinger
For those who want to go beyond the regular and documented scope of Windows operating system support and really write a contemporary virtual display driver:

  • Write a USB bus driver which emulates a USB bus with connected display device (e.g. DisplayLink USB display).
  • Then install the WDDM IddCx Indirect Display sample [from the WDK] on the virtual USB driver’s emulated display device.

Voila - there is your virtual display!

So, I added a USB Bus Emulator driver project to my solution.

Hint: I heard that the above named IddCx sample can supposedly even be operated without hardware (2) when starting Windows with “Driver Signature Enforcement” disabled (F7).

Hmm, I assumed that the emulated USB Bus eliminated the need for physical hardware…?


And then a bit later, I discovered the following discussion involving Doron Holan and Marcel Ruedinger, leading me to think I didn’t need the USB Bus Emulator driver after all…:

Doron Holan

You need a root enumerated bus driver which will then enumerate a child display device(s).

Marcel Ruedinger
Doron’s approach is correct and would work, but usually it is not necessary in a WDDM IddCx driver.
Typically it is not the graphics adapter itself, but the display monitor which is hot-plugged.
The display monitor is a child device object of the WDDM IddCx Adapter device object.
The IddCx framework itself (namely IndirectKmd.sys kernel mode filter driver) acts as a bus driver creating a child device object (PDO) for each hot-plugged display monitor.
In a WDDM IddCx driver, a new display monitor child device object is created using IddCxMonitorCreate(…).
After being created, it can be hot-plugged using IddCxMonitorArrival(…).

Current Status:

I have a driver based upon the IddSampleDriver from the latest WDK, signed with a test cert. With Driver Signature Enforcement disabled on the Windows 10 Pro [1903] VM, I’m able to install it successfully (at least, there are no obvious signs otherwise – I wasn’t ever able to get driver install debugging working).

Nothing shows up in Device Manager until I run the test program, which just calls SwDeviceCreate to enumerate the device and then waits for a key press to exit. Once enumeration has completed successfully, the device shows up in Device Manager under “Other devices” with the /!\ icon. Looking at Properties and Details, the device appears to be in an unconfigured or misconfigured state (3).

I’m at a disadvantage, because I haven’t worked on Windows drivers before. I don’t know what to expect, and sometimes it’s difficult to know whether something is a real problem or my expectations are just wrong. Books and documentation are helping, but it’s been slow going.

Questions:

  • Is the ‘not configured correctly’ status a cause or a symptom? The vanilla IddSampleDriver project gives the same behavior/result.

  • Running IddSampleTest.exe, I never see WUDFHost.exe come up in the remote debugger after SwDeviceCreate is called. The creation callback in the test app seems to get a non-zero handle passed in. Shouldn’t I see the driver being loaded, or is this due to lack of physical hardware? Not sure what to expect here…

Footnotes:

(1) At least for now. The virtual display(s) may eventually be backed by something. TBD

(2) I took this to mean that using this approach, the “virtual display” driver I’m trying to create didn’t need to be backed by hardware. However, now I realize Marcel may have been saying that that’s only possible during testing, when Driver Signature Enforcement is turned off.

(3) This device is not configured correctly. (Code 1) This operation returned because the timeout period expired. To find a driver for this device, click Update Driver.

The long story story can be cut very short:
After much more than a decade (since the release of Windows Vista), Microsoft has finally released a reasonable WDK display driver sample again. It is called IndirectDisplay and it is based on UMDF. It even seems to support virtual displays. We haven’t fully tested it yet, but it seems that they did it right this time. Looks like back in the old days of Windows XP: With a reasonable amount of effort, everybody can now write a virtual display driver for Windows 10 again. All these cumbersome workarounds which I described here during the last decade should not be needed any more in the future …

Marcel Ruedinger
datronicsoft

That’s really good news – thanks for sharing it.

My next wish-list item is for the audio team to create a simple Virtual Audio Driver. The current SYSVAD sample basically demonstrates every feature the Audio Engine supports, and runs to about 55,000 lines of code. What people WANT (based on my email) is a simple plumbing hookup to allow custom filtering in user-mode, and that takes about 6,000 lines of code.

@Tim_Roberts said:
That’s really good news – thanks for sharing it.

My next wish-list item is for the audio team to create a simple Virtual Audio Driver.
The current SYSVAD sample basically demonstrates every feature the Audio Engine supports, and runs to about 55,000 lines of code.
What people WANT (based on my email) is a simple plumbing hookup to allow custom filtering in user-mode, and that takes about 6,000 lines of code.

I’ve started working on a virtual audio driver, and have spent several days now trying to digest the SysVAD source code. Agreed, SYSVAD is way too much functionality crammed into a single example.

I’m intrigued by your mention of user-mode. I was able to create a UMDF virtual display driver based upon the IddCx sample, but all my search results for audio drivers seem to be Kernel Mode. Is there any UM support that you’re aware of?

No. Even though the Audio Engine is a user-mode process, audio drivers are all kernel mode. The typical audio driver doesn’t actually participate in streaming. It creates a DMA circular buffer and configures the hardware, then the Audio Engine process and the audio hardware spend their lives chasing circular buffer pointers without ever requiring kernel transitions.

I’ve started working on a virtual audio driver, and have spent several days now trying to digest the SysVAD source code.

Here’s a hint. If you can go back to the Windows 8.1 DDK, the MSVAD sample does everything you need without the overhead of SysVAD, and is much easier to comprehend.

the MSVAD sample does everything you need without the overhead of SysVAD

Fantastic! Thanks!

OK, I created a WaveCyclic-based driver from the Windows 8.1 DDK, the MSVAD sample as you suggested. The intent is to pipe audio from a virtual “output” endpoint (e.g. “Speaker”) to an “input” endpoint (e.g. “Line In” or “Microphone”)

I created both Topology and WaveCyclic filters (with no nodes; just straight-through connections) and I’m able to play audio to the Playback tab, but nothing gets through to the Recording tab.

Along the way, I read various things about WaveRT. Besides being recommended over WaveCyclic, the WaveRT portcls also does most of the DMA heavy lifting. I decided to bite the bullet, and pare down the SysVAD sample. Many weeks later, I have another virtual audio driver candidate (this one with some nodes), and despite also looking at a number of other MSVAD/SYSVAD-derived projects on GitHub, audio data goes in, but doesn’t come out (as in the WaveCyclic driver).

I’ve learned a lot, but there’s still lots of unanswered questions. Here’s what my current driver looks like:

/*
 * Physical connection table - wave <=> topology bridge connections.
 *
 *                                    ╭╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╮
 *              KSPIN_TOPO_DATA_INPUT ╎  1╭╌╌╌╌╌╌╌╌╮   1╭╌╌╌╌╌╌╌╌╮   ╎ KSPIN_TOPO_IVA_OUTPUT
 * (from wave pin 3) ╌╌╌╌╌╌╌╌╌╌╌╌╌╌> ╾┼╌╌╌┤ Volume ├╌╌╌╌┤  Mute  ├╌╌╌┼╼ ═════════════════════> (Playback tab)
 *                                 P0 ╎   ╰╌╌╌╌╌╌╌╌╯0   ╰╌╌╌╌╌╌╌╌╯0  ╎ P2
 *                                    ╎       N0     N2     N1       ╎
 *               KSPIN_TOPO_IVA_INPUT ╎         1╭╌╌╌╌╌╌╌╌╮          ╎ KSPIN_TOPO_DATA_OUTPUT
 * (Recording tab) ════════════════> ╾┼╌╌╌╌╌╌╌╌╌╌┤ Volume ├╌╌╌╌╌╌╌╌╌╌┼╼ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌> (to wave pin 1)
 *                                 P1 ╎          ╰╌╌╌╌╌╌╌╌╯0         ╎ P3
 *                                    ╰╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯
 *                                            Topology Filter
 *
 *                                    ╭╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╮
 *              KSPIN_WAVE_DATA_INPUT ╎         1╭╌╌╌╌╌╌╌╮          ╎ KSPIN_WAVE_SINK_OUT
 * (from topo pin 3) ╌╌╌╌╌╌╌╌╌╌╌╌╌╌> ╾┼╌╌╌╌╌╌╌╌╌╌┤  ADC  ├╌╌╌╌╌╌╌╌╌╌┼╼ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌> "Recording Control"
 *                                 P1 ╎          ╰╌╌╌╌╌╌╌╯0         ╎ P0
 *                                    ╎      N2     N0      N1      ╎
 *                 KSPIN_WAVE_SINK_IN ╎  1╭╌╌╌╌╌╌╌╮    1╭╌╌╌╌╌╌╌╮   ╎ KSPIN_WAVE_DATA_OUTPUT
 *              ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌> ╾┼╌╌╌┤  SRC  ├╌╌╌╌╌┤  DAC  ├╌╌╌┼╼ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌> (to topo pin 0)
 *                                 P2 ╎   ╰╌╌╌╌╌╌╌╯0    ╰╌╌╌╌╌╌╌╯0  ╎ P3
 *                                    ╰╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯
 *                                             WaveRT Filter
 */

I’ve tracked down and fixed a number of implementation bugs, but if my basic design is incorrect, I’m going to thrash. Am I even close to a solution, or am I completely on the wrong track?

Forgot to mention… in both drivers, audio data is being pumped, but I’m not doing anything in terms of manually copying it between capture and render streams. I’m trying to accomplish that via connections…

/*
 *                               Client Reads          Audio Device
 *                               from Buffer         Writes to Buffer
 *                                   ↑↑↑                   ↓↓↓
 *            ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┬╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┬╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
 *  CAPTURE   ╎                                │░░░░░░░░░░░░░░░░░░░░░░░░░│                   ╎
 *  stream    ╎                             ──>│░░░░░░░░ D A T A ░░░░░░░░│──>                ╎
 *            ╎                                │░░░░░░░░░░░░░░░░░░░░░░░░░│                   ╎
 *            └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┴╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┴╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
 *            ↑                                ↑                         ↑                   ↑
 *         Start of                      Read Position            Record Position          End of
 *          Buffer                       (_writeOffset)            (_playOffset)           Buffer
 *            ↓                                                                              ↓
 *            |<╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶ (_dmaBufferSize) ╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶>|
 *            ↑                                                                              ↑
 *         Start of         Play Position             Write Position                       End of
 *          Buffer          (_playOffset)             (_writeOffset)                       Buffer
 *            ↓                   ↓                         ↓                                ↓
 *            ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┬╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┬╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
 *  RENDER    ╎                   │░░░░░░░░░░░░░░░░░░░░░░░░░│                                ╎
 *  stream    ╎                ──>│░░░░░░░░ D A T A ░░░░░░░░│──>                             ╎
 *            ╎                   │░░░░░░░░░░░░░░░░░░░░░░░░░│                                ╎
 *            └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┴╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┴╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
 *                                            ↓↓↓                  ↑↑↑
 *                                        Audio Device        Client Writes
 *                                      Reads from Buffer       to Buffer
 */

If you’re not copying, then what do you expect the capture endpoint to deliver? Are your ReadDMA and WriteDMA functions getting called?

I had been hoping to accomplish this simply by routing the stream from input to output using pin and node connections. It hasn’t worked for me (using WaveCyclic or WaveRT), and based upon your question, I’m guessing that it’s not possible to do it that way.

I’m using WaveRT, and this weekend I tried having both RENDER and CAPTURE streams share the same buffer, with the capture stream ‘following’ the play position of the render stream. In debugging that, I quickly realized that, (in my test setup, at least), the format and buffer size of the two streams don’t match. So, unless I can coerce them to be identical, I’ll have to use two buffers, and handle format conversion myself.

I’d been hoping to avoid that, since there are lots of combinations…

Yes, sysvad advertises only stereo on the speaker, but mono and stereo on the microphone, and the system usually picks mono. I don’t think it’s practical to use a single buffer. What purpose would it serve? Usually in a sysvad-based device, you want to do some processing on the data anyway.

I don’t think it’s practical to use a single buffer. What purpose would it serve?

I’m trying to create a virtual pipe/cable that just routes render directly to capture. We use similar functionality in the Mac and Linux versions of our product.

Unlike WaveCyclic, WaveRT’s interface didn’t require you to implement CopyTo/CopyFrom functions. I wanted to keep things as simple as possible, and since this device is virtual, there’s no need for me to copy anything to/from hardware. WaveRT handles copying data to and from the audio engine.

My hope was that RENDER would write data into the shared buffer, and CAPTURE would read it back out. All I’d need to do was ‘slave’ the capture stream’s PlayPosition to the render stream’s (I’m using the words ‘leader’ and ‘follower’ going forward). It made sense for the capture stream to just mirror whatever format the render stream was created with. Unfortunately, capture seems to get created first. For our use-case, maybe I can just limit streams to a single high-quality format.

By the way, I’m immensely grateful for your replies. They’ve helped me tremendously.

Are you aware that the Windows audio subsystem provides that functionality natively for almost any hardware? That’s what the WASAPI loopback endpoint can do.

The idea is to ‘play’ voice data coming across the network to a virtual render endpoint. Then, other apps (Skype, Zoom, etc) could use the corresponding capture endpoint for audio input.

Are you aware that the Windows audio subsystem provides that functionality natively for almost any hardware? That’s what the WASAPI loopback endpoint can do.

I did ask about this loopback capability in my initial research. The developer that owns the networked audio processing indicated that he’s already using loopback somewhere, but that there had been issue(s) that led him to propose a dedicated audio driver.

At the time I was new+busy, so I didn’t press for details. But I certainly can re-examine that if you think it sounds fishy.

The original justification for writing a driver was that we thought we needed (at least) a virtual capture endpoint. I need to read up on the native WASAPI loopback functionality to make sure that assumption was correct.

Now I’m wondering whether there’s a possible hybrid approach where:

  • the driver just implements one or both endpoints, with no copy logic
  • the rendered audio stream is echoed to the capture endpoint via native WASAPI loopback functionality

There’s no good way to “feed” the capture endpoint from outside. If you want to provide fake data to clients, then you’re going to need a driver.

I would point out that all the cool audio driver kids, including several very helpful members of the Microsoft audio team, hang out on the wdmaudiodev mailing list at https://www.freelists.org/list/wdmaudiodev.

I would point out that all the cool audio driver kids, including several very helpful members of the Microsoft audio team, hang out on the wdmaudiodev mailing list

Fantastic. I’ll check it out. Thanks again, Tim! :slight_smile: