A Guide to Kernel Mode Audio Device Driver Development in Windows: Building a Virtual Audio Cable with a Custom WDM Driver
Introduction
This article aims to shed light on the process of developing a kernel mode audio driver, specifically a virtual audio cable, which serves as a conduit for audio data between applications.
Kernel mode drivers operate at the core level of the Windows operating system, having direct access to system memory and hardware. This high level of access allows these drivers to perform efficiently and with great speed, but it also requires a careful and thorough approach to ensure stability and security. Kernel mode driver development is a specialized field that demands a deep understanding of the Windows Kernel, its APIs, and the driver development lifecycle.
Audio drivers in Windows are the software components that enable the operating system to communicate with audio hardware devices. They are responsible for processing audio streams and ensuring that users can both capture and render audio seamlessly. However, beyond physical hardware, there exists a need for virtual audio devices that can route audio between different software applications without the need for external hardware. This is where our virtual audio cable driver comes into play.
The concept of a virtual audio cable is straightforward: it acts as an invisible link that connects audio output from one application to the audio input of another. This can be particularly useful for recording the audio output of a program directly into another, such as recording a live stream into an audio editing application.
In this article, we will delve into the development of a simple Windows Driver Model (WDM) audio driver that creates two virtual audio devices: a virtual speaker (rendering device) and a virtual microphone (capturing device). These devices will communicate through a custom-implemented ring buffer, allowing audio data to be seamlessly transferred from the virtual speaker to the virtual microphone, effectively mimicking the functionality of a physical audio cable.
By the end of this article, you will have a comprehensive understanding of how to develop a virtual audio cable driver, how to handle inter-device communication, and how to manage audio streams within the Windows kernel environment. We will also provide code snippets from the driver we developed, based on the simpleaudiosample provided by Microsoft, to illustrate the concepts discussed.
Setting Up the Development Environment
Before diving into the code and the intricacies of driver development, it’s crucial to set up a proper development environment. This environment will provide you with the tools and resources needed to create, build, and test your kernel mode audio driver. For a more detailed guide on setting up your development environment and cloning the sample code, you can refer to the official Microsoft documentation: Setting Up Your Development Environment
The base of our virtual audio cable driver is the simpleaudiosample provided by Microsoft in their Windows-driver-samples repository. To get started, you will need to clone this repository to your local machine. Use the following Git command:git clone https://github.com/Microsoft/Windows-driver-samples.git
Navigate to the audio/aimpleaudiosample directory to find the sample code that we will be referencing and building upon.
The simpleaudiosample provided by Microsoft is an excellent starting point for understanding the basics of audio driver development in Windows. It's a part of the larger Windows-driver-samples repository, which contains numerous examples of different types of drivers. This sample demonstrates how to create a minimal WaveRT (Wave Real Time) audio driver using the Windows Driver Model (WDM).
The Windows-driver-samples repository is a comprehensive collection of driver samples that developers can use to learn about driver development or as a starting point for their own projects. The samples are organized by technology and usage, making it easy to find relevant examples.
The simpleaudiosample is structured to demonstrate the basic components and flow of a WaveRT audio driver. Here are the key components you'll encounter when examining the sample:
- Driver Entry Point: This is where the driver’s execution begins. The sample provides a straightforward entry point that sets up the driver object.
- Device Add Routine: When a new device is detected by the system, this routine is called to create and initialize device-specific structures.
- WaveRT Interface: The sample implements the WaveRT interface, which is a low-latency audio interface used in Windows. It handles the audio stream and communicates with the audio engine.
- DMA (Direct Memory Access) Buffer Management: The sample includes basic management of DMA buffers, which are used for efficient data transfer between the audio hardware and system memory.
The simpleaudiosample creates two distinct devices: a virtual speaker (render device) and a virtual microphone (capture device). These devices are not backed by physical hardware but are recognized by the Windows operating system and user applications as legitimate audio endpoints.
The virtual speaker is designed to act as an audio output device. Applications can select this device as their playback target, and any audio they output will be sent to this virtual speaker. From a system perspective, it functions just like a physical speaker, but instead of producing sound, it routes audio data to the virtual microphone.
Conversely, the virtual microphone serves as an audio input device. Other applications can choose this virtual microphone as their recording source. It receives the audio data from the virtual speaker and passes it along to the application that has selected it as the input device.
Get Kohei Otsuka’s stories in your inbox
Join Medium for free to get updates from this writer.
In the upcoming sections, we will delve into the design and implementation of our virtual audio cable, building upon the simpleaudiosample. We will explore how to create a virtual speaker and a virtual microphone, and how to enable them to communicate via a custom ring buffer to achieve the functionality of a virtual audio cable.
Virtual Audio Cable Design
The core of virtual audio cable design lies in the communication between the virtual speaker and the virtual microphone. To facilitate this, we need a mechanism that allows for the efficient and reliable transfer of audio data between the two devices. This is where the ring buffer comes into play.
The below image shows the part which is extended newly with green colour(The circular buffer and its Input/Output).
The audio ring buffer is implemented in file
The Ring Buffer Implementation
The provided code outlines the implementation of a ring buffer for audio data communication between a virtual speaker and a virtual microphone. Let’s break down some of the key parts of the implementation.
The AudioRingBuffer::InitializeInput is responsible for initializing the ring buffer with the audio format properties for the input streams. It sets up the format-specific parameters such as the number of channels, bits per sample, and samples per second and allocate memory for storing the input audio data.
The AudioRingBuffer::InitializeOutputis responsible for sets up the output audio format which is used when converting the stored input audio data to output audio format.
In the simpleaudiosample code, audio formats are negotiated and decided through a process that involves the interaction between the audio driver and the Windows audio system. This negotiation ensures that the audio driver can produce or capture audio data in a format that both the driver and the audio system can understand and process.
The relevant part of the codes which specify the audio format for speaker/microhones are here and here.
The AudioRingBuffer::WriteData function is used to write audio data into the ring buffer. It handles partial frames and manages the write pointer, ensuring that it wraps around the buffer correctly. The function is called from minwavertstream.
The AudioRingBuffer::ReadData function reads data from the ring buffer and provides it to the requesting component (the virtual microphone) with the expected output audio format. It also handles partial frames and ensures that the read pointer wraps around correctly.
If the audio formats for the virtual speaker and microphone differ, the driver may need to perform on-the-fly format conversion.
The current implementation assumes the bit depth of output audio to be 32 bits, hence it is using long,
long* dataOut = reinterpret_cast<long*>(buffer);
for (ULONG channel_idx = 0; channel_idx < m_ChannelCountOut; ++channel_idx)
{
dataOut[channel_idx] = static_cast<long>(dataBuffer[channel_idx] << 16);
}if it needs to handle other bit depth, it should get the negotiated bit depth parameter like other parameter (e.g. m_ChannelCountOut), and convert it accordingly in the AudioRingBuffer::ReadData
The buffer is filled with silence if there’s an underrun (i.e., when the buffer is empty and there’s no data to read).
if (m_WriteFrameId == m_ReadFrameId) {
// buffer is empty, fill with silence
RtlZeroMemory(buffer, length);
KeReleaseSpinLock(&m_Lock, oldIrql);
return TRUE;
}The function is called from minwavertstream.
Throughout the code, spin locks are used to ensure thread safety when accessing and modifying the ring buffer. The KeAcquireSpinLock and KeReleaseSpinLock functions are used to protect critical sections of the code.
Testing the Virtual Audio Devices
Testing virtual audio devices is an essential step in driver development to ensure that the driver operates correctly and that audio data is transferred seamlessly between the virtual speaker and microphone. For detailed information on driver testing and the tools available for Windows drivers, refer to the official Microsoft documentation on driver testing: Testing a Driver
Conclusion
Developing kernel-mode drivers for virtual audio devices is a complex task that requires a deep understanding of the Windows audio infrastructure and kernel programming. Based on the simpleaudiosample from Microsoft, this article demonstrates how to create a pair of virtual audio devices that communicate through a custom ring buffer, effectively acting as a virtual audio cable.
While the challenges of kernel-mode driver development are significant, the rewards of creating powerful and flexible audio routing solutions are equally compelling. With careful design, rigorous testing, and a commitment to quality, developers can deliver virtual audio device drivers that enhance the audio capabilities of the Windows operating system.











