Help with design decision (using sysvad virtual driver as base)

How did you set the format? Are you dumping the format data to make sure Audio Engine has really agreed to your format? Is your wave data floating point data or integer? Since it’s 32 bits per sample, it could be either. The sine generator always generates integer data. If you want to send floats, you have to advertise that in the WAVEFORMATEXTENSIBLE, using KSDATAFORMAT_SUBTYPE_IEEE_FLOAT instead of KSDATAFORMAT_SUBTYPE_PCM.

Hi Tim,


Finally I could manage to get audio buffers from sysvad in real time and get notification on client app when other apps send data to sysvad speaker.

I’m using the following approach to capture audio data and save the audio data into a file:

From the client app:

1 - Create a thread and wait in a loop na event being signaled by the driver when a 8k buffer is completely filled with data from dmaBuffer
2 - Event signed, send a async IOCTL to get the data filled by the driver.
3 - Get the data received in a completion port thread with GetQueuedCompletionStatus function
4 - Copy buffer received into a final bigger buffer
5 - When the final buffer is totally full, save it in a wave file.


From the driver:

1 - ReadBytes calls a aux function I created, CopyBytes, to copy data from DmaBuffer to the private cyclic buffer.
2 - CopyBytes copies data from dmaBuffer until the cyclic buffer is full
3 - When cyclic buffer is full, sign the event to wake up the thread from client app and, get the data through async IOCTL
4 - If there’s any remaining bytes, copy them into the beginning of the cyclic buffer, increment the bytes copied
on buffer’s position and start the process over again.


The client app is getting buffers in the correct order but, when I join the buffers received together and save them in a wave file, there's a kind o "pop" sound between the buffers that deteriorates the final sound quality.
Strangely, I'm saving the cyclic buffer from driver's side by using the m_SaveData.WriteData() function and the sound it's OK.
Do you have any suggestion about what can be causing this problem?
Thanks,

The “pop” indicates that the data is not contiguous. I’m concerned about your “from the driver” steps there. You said you only signal the app when the cyclic buffer is full, but if there’s something remaining, you copy it into the beginning of the cyclic buffer. Doesn’t that mean you’ve now destroyed early bytes in that buffer that the application hasn’t read yet? The purpose of the circular buffer is to allow the river to continue to accumulate data until the app can pull its chunk out. Perhaps you should signal the app when the buffer is half-full or 3/4-full, so you have room to add additional data.

Thanks for your help, worked perfectly!

Hello gentlemen,

We are using Sysvad example to implement our noise remover.
We made a POC where a user program receives the Sysvad speaker stream and save in a file (insted of the file being saved by the Sysvad itself).
This is working fine.

However we are facing some difficulties to implement the microphone part. We tried to use the same logic as speaker but the result is not working as it should.

What we are doing initially is:

1 - the application sends a 800k buffer to the Sysvad (the WAV file data with 1 channel, 16 bits, 48000hz which is the same as the Sysvad microphone)
2 - in the WriteBytes method we use the ByteDisplacement as the number of bytes we copy from the buffer received to Sysvad m_pDmaBuffer
3 - start the Voice Recorder and record the audio received from the Sysvad microphone

But the recorded sound has poor sound quality, is full of gagging and glitches and at a certain moment, it looks like the noise of a modem connection.
Is there anything else we need to control?

Our WriteBytes implementation:

VOID CMiniportWaveRTStream::WriteBytes
(
    _In_ ULONG ByteDisplacement
)
/*++

Routine Description:
  This function writes the audio buffer using a received buffer from user application

Arguments: 
  ByteDisplacement - # of bytes to process.

--*/
{
	ULONG bufferOffset = m_ullLinearPosition % m_ulDmaBufferSize;
	ULONG runWrite = 0;

	if (m_pExtensionData != NULL && m_pDmaBuffer != NULL) {

		// Is a pointer to buffer's start that persists over each iteration
		// and maps the audio starting point to be sent to the system
		BYTE* sendIni = m_pExtensionData->MicBufferSendIni;
		
		// Is a pointer to buffer's end that persists over each iteration
		// and maps the audio ending point to be sent to the system
		BYTE* sendEnd = m_pExtensionData->MicBufferSendEnd;

		// maps how many bytes left in the buffer to be sent
		runWrite = (ULONG)(sendEnd - sendIni);

		while (ByteDisplacement > 0)
		{
			// ensure we never read beyond our buffer
			LONG minWrite = min(ByteDisplacement, min(m_ulDmaBufferSize - bufferOffset, runWrite));
			
			if (minWrite <= 0) {
				break;
			}

			RtlCopyMemory(m_pDmaBuffer + bufferOffset, sendIni, minWrite);
			bufferOffset = (bufferOffset + minWrite) % m_ulDmaBufferSize;
			ByteDisplacement -= minWrite;
			runWrite -= minWrite;
			
			// slide the pointer to the next piece of buffer to be sent
			sendIni += minWrite;
		}

		// slide the initial position to be sent in the next run 
		m_pExtensionData->MicBufferSendIni = sendIni;
	}
}

Here is the MJ function part that receive the buffer from the application:

{

.....

		// Reading client app input/output buffer information 
		systemBuffer = (BYTE*)_Irp->AssociatedIrp.SystemBuffer;
		
		// the buffer size informed by the application
		inputBufferLength = stack->Parameters.DeviceIoControl.InputBufferLength;

		//Get pointer to the device extension reserved to store our data (copy of cyclic buffers) 
		ExtensionData = (PNOISE_DATA_STRUCTURE)((PCHAR)_DeviceObject->DeviceExtension + PORT_CLASS_DEVICE_EXTENSION_SIZE);

		if (inputBufferLength > 0 && ExtensionData != NULL && systemBuffer != NULL) {
			DPF(D_TERSE, ("***Input received of %lu bytes", inputBufferLength));
			
			// copy the received buffer to our structure
			RtlCopyMemory(ExtensionData->MicBuffer, systemBuffer, inputBufferLength);
			
			// set the relative buffer end (is less or equal buffer size on our example)
			ExtensionData->MicBufferEnd += inputBufferLength;
			
			// maps the buffer's end position to be read on the WriteBytes function
			ExtensionData->MicBufferSendEnd = ExtensionData->MicBufferEnd;
			
			bufLen = inputBufferLength;
		}

		_Irp->IoStatus.Information = bufLen;
		_Irp->IoStatus.Status = STATUS_SUCCESS;
		IoCompleteRequest(_Irp, IO_NO_INCREMENT);

		return STATUS_SUCCESS;
		break;

.....
}

Is there any other thing we need to take care?

Thanks in advance!
André Fellows

1 Like

@andrefellows what is PNOISE_DATA_STRUCTURE

That is his device context structure, custom to his driver. It has all the data he needs to keep track of to do his work.

1 Like

@Tim_Roberts How can i create it.Is the structure the same as _PortClassDeviceContext. I have seen above and you call the GetDeviceContext but where is GetDeviceContext function

Do you have any driver experience at all? These are very fundamental questions. EVERY driver has a context structure that holds all of the data for each device instance. In the case of a port class driver, things are more complicated because the port (from Microsoft) and the miniport (provided by you) act as one device and share one context. Port class creates the context, but it lets you tack on extra space for your own use, in PcAddAdapterDevice.

The device context is stored in the DEVICE_OBJECT in the DeviceExtension field. In the case of a port class driver, your context starts after the port class section, and we know that part is PORT_CLASS_DEVICE_EXTENSION_SIZE bytes long. So, this line from above finds his part of the extension:

ExtensionData = (PNOISE_DATA_STRUCTURE)((PCHAR)_DeviceObject->DeviceExtension + PORT_CLASS_DEVICE_EXTENSION_SIZE);

In my port class drivers, I create a function called GetDeviceContext to do exactly that so I don’t have to type that repeatedly.

1 Like

@Tim_Roberts.I have no previous driver knowledge.Sorry for my bad English. In school i am not learning about this. I’m groping for it. As a beginner i am trying to follow the available examples to better understand this problem. Sorry for bothering you. Where can I see more examples.Is there any other way to send audio data to sysvad and write it to writebytes without using ioctl does i get an advice to use this

…to better understand this problem.

What problem? You haven’t told us anything about what goal you’re trying to achieve.

… send audio data to sysvad and write it to writebytes without using ioctl …

The link you included shows how to send data to a speaker endpoint. WriteBytes is used to manage data for the microphone endpoint. Totally separate paths. You need to think about what you have. Sysvad is a fake speaker that writes the speaker data to file, and a fake microphone that generates a sine wave. That’s what it does. To do anything else, you have to write the code to do it, and that means inventing some kind of “back door” to get data in and out.

1 Like

@Tim_Roberts I have read many of your answers to make the audio transition from user mode to application (ex: skype), I am actually copying the exact same code contained in the related questions and I don’t understand Concepts or constructs, how to implement it into code as I asked above. Can you please give me some sample projects. Thank you for the answer.

You can see from the rather good chart above that there a lot of pieces to this, and they all have to work together. It’s complicated, and it has to run in real-time. If you don’t have experience writing audio applications AND experience writing drivers, then you will never make this work. Sorry to be blunt. Even the big companies hire people to do this kind of thing.

There are no samples. Because so many people want to do this, I’ve suggested for the last 15 years that the Microsoft audio team create a much simplified version of SysVad that has external hooks to circular buffers, but so far they’ve been busy doing real work.

You need to put circular buffers in SysVad. You need to add ioctls that allow you pull data in and out. You need to write an application to do the “in and out” by calling those ioctls. You need to write a test application to take the place of Skype by reading and writing using the WASAPI APIs. You need to decide how to handle volume and mute controls. You need to figure out how much of SysVad you can delete. None of those pieces are easy.

1 Like

@Tim_Roberts Thanks sir for advice.

@Tim_Roberts can i use audio virtual cable to replace ioctl for this problem. Thanks you for answer.

Virtual Audio Cable certainly lets you route sound from Skype to another user-mode application. That doesn’t “replace ioctl”, that eliminates the entire need for a custom driver. It becomes the boxes in blue up above.

1 Like

Hi Tim, it’s me again :slight_smile:

Just a curiosity. Do you think it’s possible to implement this same solution by using a filter driver attached into a real audio driver?

My guess is no because all audio data that is sent by applications goes into MS Audio Engine through Audio APIs and, attaching a filter driver into a real audio driver we can listen some IRPs but we cannot access audio buffers.

if it’s possible, what would be the pros and cons for this alternative?

Thanks!

… this same solution …

You don’t say what solution you’re talking about. If you’re talking about routing data to and from a monitoring application, then the answer is “no”. All modern hardware audio drivers use the WinRT model, where the hardware’s circular buffer and registers are mapped directly into the Audio Engine process. The driver is not involved in streaming in any way, so there’s nothing to intercept.

I’m talking about the solution discussed in previous messages form this thread, capture audio buffers from other apps and do some processing. Anyway, I think you already answered my question, thanks!

@Tim_Roberts Hi sir, I’m using named pipe instead of ioctl and it’s seem to be working fine. Are there any disadvantage of using named pipe instead of ioctl? Thank you.