Seeking Assistance: How to Create Virtual Input and Output Devices in Windows 11

Hello everyone,

I hope this message finds you well. I'm currently working on a project where I need to create virtual microphone and speaker devices (virtual input and output devices) on Windows 11. My goal is to develop a solution that allows audio routing between applications without the need for physical audio hardware.

Here's what I'm aiming to achieve:

  • Virtual Microphone (Input Device): An emulated microphone that can feed audio data from a source (e.g., an audio file or another application) into applications that accept microphone input (e.g., Zoom, Skype).

  • Virtual Speaker (Output Device): A virtual speaker that can capture audio output from applications and route it elsewhere (e.g., to a recording application or for processing).

What I've Explored So Far:

  1. Windows Driver Kit (WDK) with C++:

    • I considered developing a kernel-mode driver using the WDK.
    • Explored the SYSVAD (System Virtual Audio Driver) sample provided by Microsoft.
    • Realized that kernel-mode driver development is complex and poses risks to system stability.
  2. User-Mode Alternatives:

    • Looked into using Virtual Audio Cable software to create virtual devices.
    • Explored the Windows Audio Session API (WASAPI) for loopback capture and rendering.
    • Considered creating an application that routes audio between input and output streams.

Challenges I'm Facing:

  • Limited Documentation and Examples:

    • There's a scarcity of comprehensive guides on creating virtual audio devices in user mode.
    • Most resources focus on kernel-mode drivers, which I prefer to avoid due to complexity.
  • Need for a User-Mode Solution:

    • I want to develop this entirely in user mode to reduce risk and simplify deployment.
    • Aim to avoid dealing with driver signing and kernel-mode debugging.
  • Integration with Windows 11:

    • Ensure compatibility with Windows 11's audio architecture.
    • Make the virtual devices appear as standard audio devices in the system settings.

What I'm Requesting Help With:

  1. Guidance on User-Mode Development:

    • How can I create virtual input and output audio devices in user mode on Windows 11?
    • Are there any APIs, SDKs, or frameworks that facilitate this process?

Thank you in advance for your assistance!

If your plan is to have this work with unmodified user-mode applications, then it cannot be done in user-mode. End of story. Audio Engine only enumerates kernel devices. Virtual Audio Cable has a kernel-mode driver, and if it will solve your problem, that's a far better solution than developing your own.

The SimpleAudioSample sample does most of what you need, and is much easier to understand than SYSVAD. SYSVAD has become a platform to demonstrate every new feature that gets added to the audio system.

If you have a certain amount of control over the applications, it is possible to write user-mode DirectShow audio sources and sinks, or even MediaFoundation filters, but the applications have to know to look there.

1 Like

Thank you for your assistance.

I have successfully executed the SimpleAudioSample and removed the beep sound. Now, I would like to capture audio from the user's microphone, process it, and then transmit the processed audio to the virtual microphone created by SimpleAudioSample. The goal is to connect this virtual device to any online meeting software, allowing it to receive the enhanced audio.

My objective is similar to Krisp, a noise removal software. They capture the user's microphone input, apply machine learning-based noise reduction, and then send the processed audio to a virtual microphone. I aim to implement a similar functionality.

Would you be able to guide me in achieving this?

I have to be a little cautious, because Krisp was one of my clients. They are smart people, and I respect the work they've done. There is certainly generic advice I can offer.

The audio processing will go on in a separate application. You don't want that kind of computing in a kernel driver. That application attaches to the live microphone and the live speaker. The meeting software would attach to your virtual audio driver for both microphone and speaker. The rest is just plumbing, and timing, of course. You're introducing latency, and have to consider that in your processing.

One of the trickier parts of the process like that is managing the buffer sizes. Too big, and the latency goes wild. Too small, and you get underruns that cause pops and clicks.

I have implemented a similar approach; however, I am unable to transmit the processed audio to the virtual microphone because the SimpleAudioSample virtual microphone does not provide an output channel. Currently, I am utilizing PortAudio for routing.

Right, that's what you have to write: a set of private ioctls that provides back-door access to the circular buffers used by ReadBytes and WriteBytes. That's the core of the job.

Hi Tim,

Thanks to the earlier guidance I now have a private IOCTL that lets a user‑mode app push PCM into a circular buffer which the render pin pulls in CMiniportWaveRTStream::WriteBytes. Sending short UTF‑16 test strings works fine, but when I stream real WAV data, I encounter the error: 0xD1 (_IRQL_NOT_LESS_OR_EQUAL) when feeding DMA buffer via private IOCTL – what am I missing?

here's my code

I’m adding a private IOCTL to the SimpleAudioSample/WaveRT driver so a user‑mode app can push PCM frames into a circular buffer that the render pin consumes in CMiniportWaveRTStream::WriteBytes.

Everything works until I pump real audio: after a few hundred writes I bluescreen with

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
Arg1: 0000000000000000 (NULL), Arg2: 2 (IRQL = DISPATCH_LEVEL), Arg3: 1 (write),
Arg4: fffff800206f48b8 (faulting PC – inside my driver)

1. Dispatch routine for the IOCTL

#define DEVICE_SEND CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_WRITE_DATA)

NTSTATUS DispatchControlDevice(PDEVICE_OBJECT dev, PIRP Irp)
{
    if (!IsCtlObj(dev)) return PcDispatchIrp(dev, Irp);

    auto irpsp  = IoGetCurrentIrpStackLocation(Irp);
    ULONG inLen = irpsp->Parameters.DeviceIoControl.InputBufferLength;
    NTSTATUS status = STATUS_SUCCESS;
    ULONG    bytes  = 0;

    switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
    {
    case DEVICE_SEND:
        if (inLen == 0) { status = STATUS_INVALID_PARAMETER; break; }
        bytes  = WriteToBackdoorBuffer((BYTE*)Irp->AssociatedIrp.SystemBuffer, inLen);
        status = (bytes < inLen) ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
        break;
    default:
        status = STATUS_INVALID_DEVICE_REQUEST;
    }
   
  // .....
 
    return status;
}

2. Non‑paged circular buffer

struct CircularBuffer
{
    BYTE*       m_Buffer;
    ULONG       m_Size;
    ULONG       m_ReadPos = 0, m_WritePos;
    KSPIN_LOCK  m_Lock;

    void Init(ULONG sz)
    {
        m_Buffer = (BYTE*)ExAllocatePool2(NonPagedPoolNx, size, 'BUF1');
        ASSERT(m_Buffer);
        m_Size     = size;
        m_ReadPos  = 0;
        m_WritePos = 0;
        KeInitializeSpinLock(&m_Lock);
    }

    NTSTATUS Put(BYTE* data, ULONG len)
    {
        if (!data || len == 0) return STATUS_INVALID_PARAMETER;
        KIRQL irql; KeAcquireSpinLock(&m_Lock, &irql);

        ULONG used  = (m_WritePos >= m_ReadPos) ? (m_WritePos - m_ReadPos)
                                               : (m_Size - (m_ReadPos - m_WritePos));
        ULONG free  = (m_Size - 1) - used;          // leave 1‑byte gap
        if (len > free) { KeReleaseSpinLock(&m_Lock, irql); return STATUS_BUFFER_OVERFLOW; }

        ULONG first = (len < m_Size - m_WritePos) ? len : m_Size - m_WritePos;
        RtlCopyMemory(m_Buffer + m_WritePos, data, first);
        if (len > first) RtlCopyMemory(m_Buffer, data + first, len - first);
        m_WritePos = (m_WritePos + len) % m_Size;

        KeReleaseSpinLock(&m_Lock, irql);
        return STATUS_SUCCESS;
    }

    NTSTATUS Take(BYTE* out, ULONG len, SIZE_T* done);
};


ULONG WriteToBackdoorBuffer(const BYTE* data, ULONG length) {
    return g_BackdoorBuffer.Put((BYTE*)data, length) == STATUS_SUCCESS ? length : 0;
}

// more code

ULONG ReadFromBackdoorBuffer(BYTE* dest, ULONG length) {
    SIZE_T written = 0;
    NTSTATUS status = g_BackdoorBuffer.Take(dest, length, &written);
    return (status == STATUS_SUCCESS || status == STATUS_NO_MORE_ENTRIES) ? (ULONG)written : 0;
}

WriteToBackdoorBuffer is just return g_BackdoorBuffer.Put(data, len) == STATUS_SUCCESS ? len : 0;


3. Render‑pin consumer

VOID CMiniportWaveRTStream::WriteBytes(ULONG ByteDisp)
{
    ULONG off = m_ullLinearPosition % m_ulDmaBufferSize;

    while (ByteDisp > 0)
    {
        ULONG run = min(ByteDisp, m_ulDmaBufferSize - off);
        ULONG got = ReadFromBackdoorBuffer(m_pDmaBuffer + off, run);

        if (got < run) {                      // underrun: pad with zeros
            RtlZeroMemory(m_pDmaBuffer + off + got, run - got);
            ByteDisp -= got;
            break;
        }
        off       = (off + run) % m_ulDmaBufferSize;
        ByteDisp -= run;
    }
}

m_pDmaBuffer is allocated from NonPagedPoolNx in StartDevice.


What I’ve verified

  • m_Buffer and m_pDmaBuffer both come from NonPagedPoolNx.
  • Spin‑lock pairs are balanced; IRQL drops back to PASSIVE_LEVEL.
  • Guard‑byte (size‑1) logic in the ring buffer should prevent overwrite.

Yet I still dereference NULL at IRQL 2. It smells like one of the pointers above sometimes becomes 0 inside the DPC, but I can’t see how. What obvious step am I missing?

Thanks for any insight,

I assume that you have collected a crash dump or have a live session with WinDbg. !analyze -v will display the complete exception info. Can you show it?

!analyze -v
Connected to Windows 10 19041 x64 target at (Wed Jul 16 07:04:59.914 2025 (UTC + 1:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
................................................................
............................................
Loading User Symbols
..............................
Loading unloaded module list
......


  •                                                                         *
    
  •                    Bugcheck Analysis                                    *
    
  •                                                                         *
    

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 0000000000000000, memory referenced
Arg2: 0000000000000002, IRQL
Arg3: 0000000000000001, value 0 = read operation, 1 = write operation
Arg4: fffff80733f248d3, address which referenced memory

Debugging Details:

Unable to load image \SystemRoot\System32\DriverStore\FileRepository\simpleaudiosample.inf_amd64_a6265879ebb33c5b\simpleaudiosample.sys, Win32 error 0n2
*** WARNING: Unable to verify checksum for test_driver.exe
Unable to load image C:\test_driver.exe, Win32 error 0n2
*** WARNING: Unable to verify checksum for test_driver.exe
Unable to load image C:\test_driver.exe, Win32 error 0n2
*** WARNING: Unable to verify checksum for test_driver.exe
Unable to load image C:\test_driver.exe, Win32 error 0n2

KEY_VALUES_STRING: 1

Key  : Analysis.CPU.mSec
Value: 10406

Key  : Analysis.Elapsed.mSec
Value: 65256

Key  : Analysis.IO.Other.Mb
Value: 0

Key  : Analysis.IO.Read.Mb
Value: 7

Key  : Analysis.IO.Write.Mb
Value: 3

Key  : Analysis.Init.CPU.mSec
Value: 4078

Key  : Analysis.Init.Elapsed.mSec
Value: 408490

Key  : Analysis.Memory.CommitPeak.Mb
Value: 98

Key  : Analysis.Version.DbgEng
Value: 10.0.27871.1001

Key  : Analysis.Version.Description
Value: 10.2505.01.02 amd64fre

Key  : Analysis.Version.Ext
Value: 1.2505.1.2

Key  : Bugcheck.Code.KiBugCheckData
Value: 0xd1

Key  : Bugcheck.Code.LegacyAPI
Value: 0xd1

Key  : Bugcheck.Code.TargetModel
Value: 0xd1

Key  : Failure.Bucket
Value: AV_simpleaudiosample!unknown_function

Key  : Failure.Exception.IP.Address
Value: 0xfffff80733f248d3

Key  : Failure.Exception.IP.Module
Value: simpleaudiosample

Key  : Failure.Exception.IP.Offset
Value: 0x48d3

Key  : Failure.Hash
Value: {5836621e-18a9-5fbb-cfc9-70a25b290d0f}

Key  : Hypervisor.Enlightenments.Value
Value: 8480

Key  : Hypervisor.Enlightenments.ValueHex
Value: 0x2120

Key  : Hypervisor.Flags.AnyHypervisorPresent
Value: 1

Key  : Hypervisor.Flags.ApicEnlightened
Value: 0

Key  : Hypervisor.Flags.ApicVirtualizationAvailable
Value: 0

Key  : Hypervisor.Flags.AsyncMemoryHint
Value: 0

Key  : Hypervisor.Flags.CoreSchedulerRequested
Value: 0

Key  : Hypervisor.Flags.CpuManager
Value: 0

Key  : Hypervisor.Flags.DeprecateAutoEoi
Value: 0

Key  : Hypervisor.Flags.DynamicCpuDisabled
Value: 1

Key  : Hypervisor.Flags.Epf
Value: 0

Key  : Hypervisor.Flags.ExtendedProcessorMasks
Value: 0

Key  : Hypervisor.Flags.HardwareMbecAvailable
Value: 0

Key  : Hypervisor.Flags.MaxBankNumber
Value: 0

Key  : Hypervisor.Flags.MemoryZeroingControl
Value: 0

Key  : Hypervisor.Flags.NoExtendedRangeFlush
Value: 1

Key  : Hypervisor.Flags.NoNonArchCoreSharing
Value: 0

Key  : Hypervisor.Flags.Phase0InitDone
Value: 1

Key  : Hypervisor.Flags.PowerSchedulerQos
Value: 0

Key  : Hypervisor.Flags.RootScheduler
Value: 0

Key  : Hypervisor.Flags.SynicAvailable
Value: 0

Key  : Hypervisor.Flags.UseQpcBias
Value: 0

Key  : Hypervisor.Flags.Value
Value: 536588

Key  : Hypervisor.Flags.ValueHex
Value: 0x8300c

Key  : Hypervisor.Flags.VpAssistPage
Value: 1

Key  : Hypervisor.Flags.VsmAvailable
Value: 0

Key  : Hypervisor.RootFlags.AccessStats
Value: 0

Key  : Hypervisor.RootFlags.CrashdumpEnlightened
Value: 0

Key  : Hypervisor.RootFlags.CreateVirtualProcessor
Value: 0

Key  : Hypervisor.RootFlags.DisableHyperthreading
Value: 0

Key  : Hypervisor.RootFlags.HostTimelineSync
Value: 0

Key  : Hypervisor.RootFlags.HypervisorDebuggingEnabled
Value: 0

Key  : Hypervisor.RootFlags.IsHyperV
Value: 0

Key  : Hypervisor.RootFlags.LivedumpEnlightened
Value: 0

Key  : Hypervisor.RootFlags.MapDeviceInterrupt
Value: 0

Key  : Hypervisor.RootFlags.MceEnlightened
Value: 0

Key  : Hypervisor.RootFlags.Nested
Value: 0

Key  : Hypervisor.RootFlags.StartLogicalProcessor
Value: 0

Key  : Hypervisor.RootFlags.Value
Value: 0

Key  : Hypervisor.RootFlags.ValueHex
Value: 0x0

Key  : SecureKernel.HalpHvciEnabled
Value: 0

Key  : WER.OS.Branch
Value: vb_release

Key  : WER.OS.Version
Value: 10.0.19041.1

BUGCHECK_CODE: d1

BUGCHECK_P1: 0

BUGCHECK_P2: 2

BUGCHECK_P3: 1

BUGCHECK_P4: fffff80733f248d3

FAULTING_THREAD: ffff860417464080

WRITE_ADDRESS: unable to get nt!PspSessionIdBitmap
0000000000000000

PROCESS_NAME: test_driver.exe

STACK_TEXT:
*** WARNING: Unable to verify checksum for test_driver.exe
Unable to load image C:\test_driver.exe, Win32 error 0n2
ffffc887270fac38 fffff8072f717c02 : ffffc887270fada0 fffff8072f57ef50 0000000000000000 0000000000000000 : nt!DbgBreakPointWithStatus
ffffc887270fac40 fffff8072f7171e6 : 0000000000000003 ffffc887270fada0 fffff8072f615340 00000000000000d1 : nt!KiBugCheckDebugBreak+0x12
ffffc887270faca0 fffff8072f5fd4c7 : ffff860417ec9960 ffff860417ece3e0 0000000000000002 0000000000000000 : nt!KeBugCheck2+0x946
ffffc887270fb3b0 fffff8072f611ba9 : 000000000000000a 0000000000000000 0000000000000002 0000000000000001 : nt!KeBugCheckEx+0x107
ffffc887270fb3f0 fffff8072f60d578 : 0000000000000000 0000000000000000 0000000000000000 0000000000000000 : nt!KiBugCheckDispatch+0x69
ffffc887270fb530 fffff80733f248d3 : ffff860414bb6ce0 0000000000000002 0000000000000000 0000000000000000 : nt!KiPageFault+0x478
ffffc887270fb6c0 fffff80733f24bc3 : fffff80733f2a6b0 ffff8604174e5000 0000000000001000 0000000000000000 : simpleaudiosample+0x48d3
ffffc887270fb720 fffff80733f21131 : ffff8604174e5000 0000000000001000 0000000000000000 0000000000000001 : simpleaudiosample+0x4bc3
ffffc887270fb760 fffff8072f4d2205 : ffff860411a5c870 ffff860414da2c50 000000000000020c ffff86041763b080 : simpleaudiosample+0x1131
ffffc887270fb7f0 fffff8072f84c5e1 : ffffc887270fbb80 000000000022a004 ffff860414bb6ce0 0000000000000000 : nt!IofCallDriver+0x55
ffffc887270fb830 fffff8072f84c21a : 000000000022a004 ffffc887270fbb80 0000000000010000 000000000022a004 : nt!IopSynchronousServiceTail+0x361
ffffc887270fb8d0 fffff8072f84b4f6 : 0000000000000000 0000000000000000 0000000000000000 0000000000000000 : nt!IopXxxControlFile+0xd0a
ffffc887270fba20 fffff8072f611305 : 0000000000000000 0000000000000000 ffff860417464080 ffff860416778c50 : nt!NtDeviceIoControlFile+0x56
ffffc887270fba90 00007ff97334d5d4 : 00007ff970a0ddab 000000aebccff778 0000000000000000 0000000000000000 : nt!KiSystemServiceCopyEnd+0x25
000000aebccff6a8 00007ff970a0ddab : 000000aebccff778 0000000000000000 0000000000000000 00007ff953433122 : ntdll!NtDeviceIoControlFile+0x14
000000aebccff6b0 00007ff971905951 : 000000000022a004 000001cb10401fd0 000000aebccff7b0 0000000000000000 : KERNELBASE!DeviceIoControl+0x6b
000000aebccff720 00007ff7ad25166e : 0000000000000000 000001cb1040201e 000000aebccff7c9 0000000000004b00 : KERNEL32!DeviceIoControlImplementation+0x81
000000aebccff770 0000000000000000 : 000001cb1040201e 000000aebccff7c9 0000000000004b00 0000000000000000 : test_driver+0x166e

SYMBOL_NAME: simpleaudiosample+48d3

MODULE_NAME: simpleaudiosample

IMAGE_NAME: simpleaudiosample.sys

STACK_COMMAND: .process /r /p 0xffff86041763b080; .thread 0xffff860417464080 ; kb

BUCKET_ID_FUNC_OFFSET: 48d3

FAILURE_BUCKET_ID: AV_simpleaudiosample!unknown_function

OS_VERSION: 10.0.19041.1

BUILDLAB_STR: vb_release

OSPLATFORM_TYPE: x64

OSNAME: Windows 10

FAILURE_ID_HASH: {5836621e-18a9-5fbb-cfc9-70a25b290d0f}

Followup: MachineOwner

Are you absolutely sure g_BackdoorBuffer.Init() has been called? How do you know? Exactly where does the crash occur? Is it one of the RtlCopyMemory calls?

g_BackdoorBuffer.Init(64 * 1024);

i have called this inside the DriverEntry in the adapter.cpp

the !analyze -v output appears messed up for me. The stack trace should point to the location where the exception happens.

The exception code does indicate a write through a NULL pointer. An actual null pointer, not a small offset from NULL like you would find if a struct member was de-referenced. So you are looking for the target buffer of a memory copy or similar. Presumably while holding your spinlock, but the code is too hard to read in this format

I’ve resolved the earlier issue. For testing, I used a .wav file, but I’m still hearing a faint glittering noise. Here’s the code I’m using to send the data —could you help me work out what might be causing it?

bool SendAudioData(const float* data, int lengthInSamples, int channels)
{
    WriteLog(L"=== Starting Audio Send ===");
    const int SHORTS_PER_WRITE = CHUNK / (sizeof(SHORT) * channels);
    const SIZE_T bufferSize   = SHORTS_PER_WRITE * sizeof(SHORT) * channels;

    // Use a vector to manage the buffer memory
    std::vector<SHORT> shortBuffer(SHORTS_PER_WRITE * channels);

    std::wstringstream ss;
    ss << L"Using IOCTL code: 0x" << std::hex << DEVICE_SEND;
    WriteLog(ss.str());

    bool hasNonZeroData = false;
    for (int i = 0; i < _min(100, lengthInSamples * channels); i++) {
        if (data[i] != 0.0f) {
            hasNonZeroData = true;
            break;
        }
    }
    WriteLog(std::wstring(L"Has non-zero data: ") + (hasNonZeroData ? L"Yes" : L"No"));

    int  processedSamples = 0;
    bool success          = true;

    while (processedSamples < lengthInSamples && success) {
        std::fill(shortBuffer.begin(), shortBuffer.end(), 0);

        int   samplesToProcess = _min(SHORTS_PER_WRITE, lengthInSamples - processedSamples);
        DWORD byteSize         = samplesToProcess * channels * sizeof(SHORT);

        // Convert float samples to signed 16-bit PCM
        for (int i = 0; i < samplesToProcess; i++) {
            for (int ch = 0; ch < channels; ch++) {
                float sample       = data[(processedSamples + i) * channels + ch];
                float scaledSample = sample * 32767.0f;
                scaledSample       = _max(-32768.0f, _min(32767.0f, scaledSample));
                shortBuffer[i * channels + ch] = static_cast<SHORT>(scaledSample);
            }
        }

        // Debug logging
        if (processedSamples == 0) {
            ss.str(L"");
            ss << L"Buffer ptr: 0x" << std::hex << shortBuffer.data();
            WriteLog(ss.str());

            ss.str(L"");
            ss << L"First 5 samples (short, per channel): ";
            for (int i = 0; i < _min(5, samplesToProcess); i++) {
                for (int ch = 0; ch < channels; ch++) {
                    ss << std::dec << shortBuffer[i * channels + ch] << L" ";
                }
            }
            WriteLog(ss.str());

            BYTE* bytes = reinterpret_cast<BYTE*>(shortBuffer.data());
            ss.str(L"");
            ss << L"First 8 bytes: ";
            for (int i = 0; i < _min(8, byteSize); i++) {
                ss << std::hex << std::setw(2) << std::setfill(L'0')
                   << static_cast<int>(bytes[i]) << L" ";
            }
            WriteLog(ss.str());
        }

        OVERLAPPED overlapped = {0};
        overlapped.hEvent     = CreateEvent(nullptr, TRUE, FALSE, nullptr);
        if (!overlapped.hEvent) {
            WriteLog(L"Failed to create event");
            success = false;
            break;
        }

        WriteLog(L"Sending METHOD_IN_DIRECT IOCTL...");

        // Pass audio data in lpInBuffer for METHOD_IN_DIRECT
        DWORD bytesReturned = 0;
        if (!DeviceIoControl(
                g_deviceHandle,
                DEVICE_SEND,
                shortBuffer.data(),  // lpInBuffer: audio data
                byteSize,            // nInBufferSize
                nullptr,             // lpOutBuffer: not used
                0,                   // nOutBufferSize
                &bytesReturned,
                &overlapped))
        {
            if (GetLastError() != ERROR_IO_PENDING) {
                g_lastError = GetLastError();
                ss.str(L"");
                ss << L"DeviceIoControl failed, error: "
                   << std::dec << g_lastError;
                WriteLog(ss.str());
                CloseHandle(overlapped.hEvent);
                success = false;
                break;
            }

            // Wait for completion
            if (!GetOverlappedResult(
                    g_deviceHandle,
                    &overlapped,
                    &bytesReturned,
                    TRUE))
            {
                g_lastError = GetLastError();
                ss.str(L"");
                ss << L"GetOverlappedResult failed, error: "
                   << std::dec << g_lastError;
                WriteLog(ss.str());
                CloseHandle(overlapped.hEvent);
                success = false;
                break;
            }
        }

        ss.str(L"");
        ss << L"Write successful, bytes: "
           << std::dec << bytesReturned;
        WriteLog(ss.str());
        CloseHandle(overlapped.hEvent);
        processedSamples += samplesToProcess;
    }

    return success;
}

What's doing the timing here? How are you handling buffer overflow or buffer underrun? This process is probably not synchronized with your driver.