I'm developing a virtual audio driver using the example from this GitHub repository.
I want to get the data from a user application and feed the audio data to the virtual driver for the capturing pipeline. I have created an IOCTL and can send audio data from the user application to the driver, which seems to be working fine as I verified by dumping it into a file.
However, I have two problems now:
After creating the IOCTL and handling the major IRP create and IRP close, I'm not getting the endpoints. I have attached my major function handling the IRP below.
How do I send audio data from adapter.cpp where I'm receiving the data to the CMiniportWaveRTStream class? I know I need to pass the data to CMiniportWaveRTStream::WriteBytes to write data to the mic.
In DriverEntry, I have created the IOCTL (IoCreateDevice) and symbolic link (IoCreateSymbolicLink). Here is my code example:
if (!NT_SUCCESS(IoCreateDevice(DriverObject, 0, &DEVICE_NAME, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &DriverObject->DeviceObject))) {
DPF_ENTER(("Siva : IO Create device failed"));
ntStatus = STATUS_UNSUCCESSFUL;
goto Done;
}
// Routines that will execute once a handle to our device's symbolic link is opened/closed
DriverObject->MajorFunction[IRP_MJ_CREATE] = MajorFunctions;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MajorFunctions;
// Routine for handling IO requests from userland
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SimpleAudioSampleDeviceControl;
DPF_ENTER(("Siva Create symbolic link"));
if (NT_SUCCESS(IoCreateSymbolicLink(&DEVICE_SYMBOLIC_NAME, &DEVICE_NAME))) {
DPF_ENTER(("Siva : Symbolic link created"));
} else {
DPF_ENTER(("Siva : Symbolic link failed"));
IoDeleteDevice(DriverObject->DeviceObject);
ntStatus = STATUS_UNSUCCESSFUL;
goto Done;
}
NTSTATUS MajorFunctions(PDEVICE_OBJECT DriverObject, PIRP Irp) {
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
UNREFERENCED_PARAMETER(DriverObject);
PAGED_CODE();
PIO_STACK_LOCATION stackLocation = IoGetCurrentIrpStackLocation(Irp);
switch (stackLocation->MajorFunction) {
case IRP_MJ_CREATE:
DbgPrint("siva: IRP_MJ_CREATE File Name: %wZ\n", stackLocation->FileObject->FileName);
break;
case IRP_MJ_CLOSE:
DbgPrint("siva: IRP_MJ_CLOSE File Name: %wZ\n", stackLocation->FileObject->FileName);
break;
default:
DbgPrint("siva: default File Name: %wZ\n", stackLocation->FileObject->FileName);
ntStatus = STATUS_INVALID_DEVICE_REQUEST;
break;
}
ntStatus = PcDispatchIrp(DriverObject, Irp);
if (ntStatus != STATUS_SUCCESS) {
DbgPrint("PcDispatchIrp inside if: \n");
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
return STATUS_SUCCESS;
}
Please guide me on how to move forward. Thanks in advance!
This belongs in the NTDEV category, not WINDBG. Perhaps the moderators can move it.
You have to pass the buffer down through the class inheritance tree. The adapter creates a CAdapterCommon object. That's where I store the circular buffers. Your CMiniportWaveCyclic derivative gets a copy of the CAdapterCommon during its Init handler using QueryInterface. You can define a method in CAdapterCommon that returns a pointer to the appropriate circular buffer.
Port class owns the first part of your device extension. Your section begins PORT_CLASS_DEVICE_EXTENSION_SIZE bytes in. You have to ask for the extra space when you call PcAddAdapterDevice in your AddDevice handler.
Hi @Tim_Roberts , thanks for your response! I have added the port class extension, and the PcAddAdapterDevice status is successful. However, I'm still getting a null extension when accessing it in the IOCTL handler. Here is the PcAddAdapterDevice call:
You're not asking for any extra space here. You need to add the size of your context to PORT_CLASS_DEVICE_EXTENSION_SIZE to get some space for yourself.
There should always be an extension. Are you creating your own device object for receiving these ioctls, or are you using the one Port Class creates?
@Tim_Roberts
I'm using the Port Class device object, which has an m_pCommon pointer sufficient for accessing the common class. However, the extension pointer is coming up as null.
How to pass the data received from an IOCTL call to the common adapter class where I have a circular buffer:
My aim is to receive data in the adapter.cpp class within an IRP_MJ_DEVICE_CONTROL handler and then pass this data to the CAdapterCommon class, where I have a circular buffer to handle the data. I'm new to driver development, so could you provide a step-by-step guide on what I need to do next? If there are any examples available, please point them out.
Hi @Tim_Roberts
Siva here again. I was able to pass the audio buffer to the common class, thanks to your support!
I have two questions:
I want to change the bits per sample from 32-bit float to 16-bit. Where should I make this change, and how can I support multiple sample rates, bits per sample, and channels?
I created a circular buffer in common.cpp. Is it better to keep it there, or should I move the circular buffer instance to miniwavrRT or miniwaveRTStream?
You have to set up the KSDATAFORMAT structures to describe your capabilities. The samples all store that in a file called xxxxwavtable.h. The one in the Simple Audio Sample (speakerwavtable.h) only supports one format at one datarate. You can look at the SysVad sample for other xxxwavtable.h examples that support a wider range.
You also might ask yourself if it's worth the trouble. Why do you need to provide other formats? The system will almost always choose the best stereo format you support, and ignore the rest.
Put the buffer(s) wherever it makes sense to you. I put mine in CAdapterCommon and added a "getter" function to IAdapterCommon, because the AdapterCommon object is available to all the child objects. That's certainly not the only architecture possible, and there is no "best".
Hi @Tim_Roberts , thank you for your help. I have successfully created an end-to-end working pipeline and have a few questions.
Firstly, I'm allocating memory for a circular buffer in the CAdapterCommon class (common.cpp) and freeing the memory in the CAdapterCommon::~CAdapterCommon destructor. However, I noticed that the CAdapterCommon destructor is never called, even when removing the subdevice or unregistering the device, leading to a memory leak.
Where is the best place to release the memory that I allocated earlier? I'm considering allocating the memory when the IOCTL call is triggered. At that time, I'm sending the first audio buffer, initiating the circular buffer object, and allocating the memory. Please advise on where I should release the memory.
Secondly, in the major function, I'm getting the file name as null in IRP_MJ_CREATE and IRP_MJ_CLOSE. I'm using SetupDiGetDeviceInterfaceDetail calls to set and get the proper name for the driver. How can I correctly set and retrieve the device name?