Thanks to you craig, I managed to make some huge progress!
- I created my own Ring Buffer which is working (not perfecty?) and put it as a member of the MiniportWaveRTStream class.
- I’m now using METHOD_IN_DIRECT in my custom IOCTL to transfer the audio buffers for now, I’m still copying the bytes inside the AssociatedIrp.SystemBuffer (I don’t know if it’s really worth it to go through all the headaches to get the MDL working since I’ll probably use Kernel Winsock later on anyway).
- Instead of trying to set a global pointer to the last stream created, I set a global pointer to its ring buffer member, and now I can set its address access it and Write bytes into it.
- When there’s not enough bytes in the Ring Buffer to read, I set the remaining to zero bytes asked by the driver to Zero (silence).
And after a lot of failures and retries (printing debug messages inside my ring buffer for example was NOT a good idea at all), I managed to get it to work, I can successfully hear the sound that’s emitted from the UDP client, but the audio quality is pretty bad: there’s permanent crackling noise during and I can’t seem to find what’s the cause! What I tried so far:
- Increase the wave format quality to 48kHz, 16 bits, 1 channel → the permanent crackling noise is still there.
- Play the received PCM audio in several user-mode applications to see if the problem is in the UDP packets/format → the incoming audio buffers are fine, no crackling, no noise, the problem isn’t in the transmission (tested in C++ using SDL and in C# using NAudio).
- Remove the spinlock in the write and/or read methods of the Ring Buffer → only made it worse.
- Since the type of bytes is unsigned char, I thought maybe the noise is coming from the negative values of the bytes inside the buffer, so I tried setting manually every negative byte in my audio buffer to be at least 0 before sending it to the Ring Buffer → only made it worse.
Is it maybe due to my Ring Buffer lacking some sort of optimization? I tried to make it the simpliest possible, using only three parameters to keep the track (Write Position, Read Position and the Count of availables bytes), here are my 2 methods Write and Read, if u can spot any imperfection I would be glad to correct it!
NTSTATUS RingBuffer::Write(_In_ BYTE* pBytes, _In_ SIZE_T nbBytesToWrite)
{
if (nbBytesToWrite > m_BufferSize) return STATUS_BUFFER_TOO_SMALL;
if (nbBytesToWrite == 0) return STATUS_SUCCESS;
NTSTATUS status = STATUS_SUCCESS;
KeAcquireSpinLock(m_SpinLock, &m_SpinLockIrql);
if ((m_WritePosition + nbBytesToWrite) - m_ReadPosition > m_BufferSize)
{
// Buffer will be overwritten, ReadPosition will have to be moved after the write
status = STATUS_DATA_OVERWRITTEN;
}
SIZE_T nbTotalBytesWritten = 0;
do
{
SIZE_T nbBytesToWriteNow = min(nbBytesToWrite, m_BufferSize - m_WritePosition);
RtlCopyMemory(m_Buffer + m_WritePosition, pBytes + nbTotalBytesWritten, nbBytesToWriteNow);
m_WritePosition = (m_WritePosition + nbBytesToWriteNow) % m_BufferSize;
nbBytesToWrite -= nbBytesToWriteNow;
nbTotalBytesWritten += nbBytesToWriteNow;
} while (nbBytesToWrite > 0);
// Set the new count of bytes which cannot exceed the buffer Size
m_BytesCount = min(m_BufferSize, m_BytesCount + nbTotalBytesWritten);
if (status == STATUS_DATA_OVERWRITTEN)
{
m_ReadPosition = m_WritePosition;
}
KeReleaseSpinLock(m_SpinLock, m_SpinLockIrql);
return status;
}
And instead of m_ToneGenerator.GenerateSine(m_pDmaBuffer + bufferOffset, runWrite);
inside CMiniportWaveRTStream::WriteBytes
I set it to:
SIZE_T bytesRead = 0;
m_RingBuffer->Read(m_pDmaBuffer + bufferOffset, runWrite, &bytesRead);
if (bytesRead < runWrite)
{
RtlZeroMemory(m_pDmaBuffer + bufferOffset + bytesRead, runWrite - bytesRead );
}
NTSTATUS RingBuffer::Read(_In_ BYTE* pTarget, _In_ SIZE_T nbBytesToRead, SIZE_T* readCount)
{
if (nbBytesToRead == 0)
{
if (readCount) *readCount = 0;
return STATUS_SUCCESS;
}
KeAcquireSpinLock(m_SpinLock, &m_SpinLockIrql);
if (m_BytesCount == 0) // buffer is empty
{
if (readCount) *readCount = 0;
KeReleaseSpinLock(m_SpinLock, m_SpinLockIrql);
return STATUS_DEVICE_NOT_READY;
}
// Ajdust the size of the bytes to read in case we don't have that much bytes in our buffer
nbBytesToRead = min(nbBytesToRead, m_BytesCount);
SIZE_T nbTotalBytesRead = 0;
do
{
SIZE_T nbBytesToReadNow = min(nbBytesToRead, m_BufferSize - m_ReadPosition);
RtlCopyMemory(pTarget + nbTotalBytesRead, m_Buffer + m_ReadPosition, nbBytesToReadNow);
m_ReadPosition = (m_ReadPosition + nbBytesToReadNow) % m_BufferSize;
nbBytesToRead -= nbBytesToReadNow;
nbTotalBytesRead += nbBytesToReadNow;
} while (nbBytesToRead > 0);
m_BytesCount -= nbTotalBytesRead;
if (readCount)
{
// We return how many bytes we could read
*readCount = nbTotalBytesRead;
}
KeReleaseSpinLock(m_SpinLock, m_SpinLockIrql);
return STATUS_SUCCESS;
}