[wdmaudiodev] Re: Virtual audio driver

  • From: Oleg Vorobiov <isnullxbh@xxxxxxxxx>
  • To: wdmaudiodev@xxxxxxxxxxxxx
  • Date: Fri, 10 Jul 2020 02:48:59 +0700

I will try to help you with virtual microphone implementation.

*1. Driver:*

I started with MSVAD micarray
<https://github.com/microsoftarchive/msdn-code-gallery-microsoft/tree/master/Official%20Windows%20Driver%20Kit%20Sample/Windows%20Driver%20Kit%20(WDK)%208.1%20Samples/%5BC%2B%2B%5D-windows-driver-kit-81-cpp/WDK%208.1%20C%2B%2B%20Samples/Microsoft%20Virtual%20Audio%20Device%20Driver%20Sample/C%2B%2B/micarray>
driver and made the following changes:

   1. Register the IO device interface (adapter.cpp)
   2. Add a handler for IRP requests of the IRP_MJ_CREATE type (adapter.cpp)
   3. Add a handler for IRP requests of the IRP_MJ_WRITE type (adapter.cpp)
   4. Do refactor for the SaveData class (savedata.h/savedata.cpp)
   5. Implement the member function CopyFrom of the
   CMiniportWaveCyclicStreamMSVAD class (basedma.cpp)
   6. Implement a wrapper for the active audio stream (intend to store a
   pointer to the active stream) that allow you to access stream object
   outside the CMiniportWaveCyclic class
   7. Capture pointer to the active stream


*> Register the IO device interface (adapter.cpp)*

NTSTATUS AddDevice(...)
{
    // code
    ntStatus =
        PcAddAdapterDevice(DriverObject
            , PhysicalDeviceObject
            , PCPFNSTARTDEVICE(StartDevice)
            , MAX_MINIPORTS
            , 0);

    if (NT_SUCCESS(ntStatus))
    {
        // code

        UNICODE_STRING DeviceRefStr = RTL_CONSTANT_STRING(L"MyDevice");
        UNICODE_STRING DeviceSymlink =
RTL_CONSTANT_STRING(L"\\Device\\MyDevice");

        ntStatus =
            IoRegisterDeviceInterface(PhysicalDeviceObject
                , static_cast<LPCGUID>(&PID_MSVAD)
                , &DeviceRefStr
                , &DeviceSymlink);

        // check for errors
    }

    return ntStatus;
}


*> Add a handler for IRP requests of the IRP_MJ_CREATE type (adapter.cpp)*

auto HandleCreate(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) ->
NTSTATUS
{
    auto IrpStack = IoGetCurrentIrpStackLocation(Irp);

    const auto IsUserSpaceAppRequest =
        !RtlCompareUnicodeString(&g_DeviceNameRefStr
            , &IrpStack->FileObject->FileName
            , TRUE);

    if (!IsUserSpaceAppRequest)
    {
        return PcDispatchIrp(DeviceObject, Irp);
    }

    *// Check if the file exists (file path stored in DeviceSymlink after
the IoRegisterDeviceInterface call)*

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

*> Add a handler for IRP requests of the IRP_MJ_WRITE type (adapter.cpp)*

auto HandleWrite(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) ->
NTSTATUS
{
    auto IrpStack = IoGetCurrentIrpStackLocation(Irp);

    const auto IsUserSpaceAppRequest =
        !RtlCompareUnicodeString(&g_DeviceNameRefStr
            , &IrpStack->FileObject->FileName
            , TRUE);

    if (!IsUserSpaceAppRequest)
    {
        return PcDispatchIrp(DeviceObject, Irp);
    }

    *// Write audio data to the DMA buffer*
    if (true)
    {
        auto Context = reinterpret_cast<PVOID>(Irp);
        ActiveStream::GetInstance()
            .Process([](PCMiniportWaveCyclicStream Stream, PVOID Context)
-> void
                {
                    auto Irp = reinterpret_cast<PIRP>(Context);

                    auto InputDataPtr = MmGetMdlVirtualAddress
(Irp->MdlAddress);
                    auto InputDataLen = MmGetMdlByteCount(Irp->MdlAddress);

                    if (InputDataPtr)
                    {
                        Stream->CopyTo(nullptr, InputDataPtr, InputDataLen);
                    }
                }
                , Context);
    }

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

*> Register handlers:*

NTSTATUS DriverEntry(...)
{
    // code
    ntStatus =
        PcInitializeAdapterDriver
        (
            DriverObject,
            RegistryPathName,
            (PDRIVER_ADD_DEVICE)AddDevice
        );

    if (NT_SUCCESS(ntStatus))
    {
        DriverObject->MajorFunction[IRP_MJ_PNP] = PnpHandler;
        DriverObject->MajorFunction[IRP_MJ_CREATE] = HandleCreate;
        DriverObject->MajorFunction[IRP_MJ_WRITE] = HandleWrite;
    }
    // code
}

*> Do refactor for the SaveData class (savedata.h/savedata.cpp)*

Remove all things related to writing audio data from the DMA buffer to the
file (don't need for our purposes)

I have removed all methods except the following:

   - Disable(...);
   - Initialize();
   - SetDeviceObject(...);
   - GetDeviceObject();
   - ReadData(...);
   - WriteData(...);


*> Implement a wrapper for the active audio stream (intend to store a
pointer to the active stream) that allow you to access stream object
outside the CMiniportWaveCyclic class*

Possible implementation:

Header:

// Forward declarations
class CMiniportWaveCyclicStream;

class Stream final
{
public:
    using StreamProcessor = void (*)(CMiniportWaveCyclicStream*, PVOID);

private:
    Stream();

public:
    static auto GetInstance() noexcept -> Stream&;

    auto Process(StreamProcessor Processor, PVOID Context) -> void;
    auto Reset(CMiniportWaveCyclicStream* NewStream) -> void;

private:
    CMiniportWaveCyclicStream*  m_Stream;
    KSPIN_LOCK                  m_StreamSpinLock;
};

Source file:

auto Stream::GetInstance() noexcept -> Stream&
{
    static Stream stream {};
    return stream;
}

Stream::Stream()
    : m_Stream(nullptr)
    , m_StreamSpinLock()
{
    KeInitializeSpinLock(&m_StreamSpinLock);
}

auto Stream::Process(StreamProcessor Processor, PVOID Context) -> void
{
    KeAcquireSpinLockAtDpcLevel(&m_StreamSpinLock);
    if (Processor && m_Stream)
    {
        Processor(m_Stream, Context);
    }
    KeReleaseSpinLockFromDpcLevel(&m_StreamSpinLock);
}

auto Stream::Reset(CMiniportWaveCyclicStream* NewStream) -> void
{
    KeAcquireSpinLockAtDpcLevel(&m_StreamSpinLock);
    m_Stream = NewStream;
    KeReleaseSpinLockFromDpcLevel(&m_StreamSpinLock);
}



*> Implement the member function CopyFrom of the
CMiniportWaveCyclicStreamMSVAD class (basedma.cpp)*_Use_decl_annotations_
STDMETHODIMP_(void)
CMiniportWaveCyclicStreamMSVAD::CopyFrom(PVOID Destination, PVOID Source,
ULONG ByteCount)
{
    UNREFERENCED_PARAMETER(Source);

    m_SaveData.ReadData(reinterpret_cast<PBYTE>(Destination), ByteCount);
}

*> Capture pointer to the active stream (see minwave.cpp, the NewStream
member function)*

    if (NT_SUCCESS(ntStatus))
    {
        // code

        *OutStream = PMINIPORTWAVECYCLICSTREAM(stream);
        (*OutStream)->AddRef();

        *OutDmaChannel = PDMACHANNEL(stream);
        (*OutDmaChannel)->AddRef();

        *OutServiceGroup = m_ServiceGroup;
        (*OutServiceGroup)->AddRef();

        *ActiveStream::GetInstance().Reset(stream); // <- capture*
    }

*2. User-space application:*

1. Open file

    const auto handle =

CreateFile(L"\\\\?\\ROOT#MEDIA#0000#{5b722bf8-f0ab-47ee-b9c8-8d61d31375a1}\\MyDevice"
            , GENERIC_WRITE
            , FILE_SHARE_WRITE
            , nullptr
            , OPEN_EXISTING
            , NULL
            , NULL);

2. Write audio data using WriteFile (in the cycle, with delay calculated
according to the audio characteristic such as samples per sec, mono/stereo,
e.t.c.)

        const auto err =
            !WriteFile(handle
                , reinterpret_cast<LPCVOID>(*audio_data_buffer*)
                , *chunck_length*
                , *&bytes_written*
                , NULL);

3. Close handle

*3. Additional changes:*

1. Pay attention to the parameres listed in miscarry.h (should match the
audio characteristics that you will render via microphone)
2. Pay attention to the miniport pin descriptors (as default they use
*KSNODETYPE_MICROPHONE_ARRAY*, but I've changed it to
*KSNODETYPE_MICROPHONE*)

It's all. I could miss something, sorry (I have no access to the latest
project version at the moment). I hope some of this is helpful for you :)

P.S. Sorry for my English.


On Fri, Jul 10, 2020 at 12:47 AM Jakob Ashtar <info@xxxxxxxxxxxxxxxxxxx>
wrote:

Hi,



I am looking for some advice and pointers on how to implement a virtual
audio driver for Windows which does what I’m describing in this diagram:



https://pasteboard.co/JgSKG4e.png



What is the quickest route to implementing this? I have looked at the AC97
audio driver sample project from Microsoft and also the SysVad project.



Currently I am looking at the AC97 project but I’m not entirely sure
how/where I get access to the speaker and microphone data.



Source:


https://github.com/microsoftarchive/msdn-code-gallery-microsoft/tree/master/Official%20Windows%20Driver%20Kit%20Sample/Windows%20Driver%20Kit%20(WDK)%208.1%20Samples/%5BC%2B%2B%5D-windows-driver-kit-81-cpp/WDK%208.1%20C%2B%2B%20Samples/AC97%20Driver%20Sample/C%2B%2B



Thanks

Jake



Other related posts: