I am sending audio frames from userland to kernel mode driver using IOCTL, it’s working so far but I am having a lot of silences each X ms. I am struggling with timings to wait between sendings and with buffer size without luck at the minute.
The code I use to send data is the following:
using (var reader = new WaveFileReader("Bontempi-B3-C6.wav"))
{
var outFormat = new WaveFormat(48000, 16, 2);
var bufferedWaveProvider = new BufferedWaveProvider(outFormat)
{
BufferDuration = TimeSpan.FromSeconds(10)
};
byte[] fileBuffer = new byte[2048];
int bytesRead;
do
{
bytesRead = reader.Read(fileBuffer, 0, fileBuffer.Length);
bufferedWaveProvider.AddSamples(fileBuffer, 0, bytesRead);
} while (bytesRead > 0);
byte[] buffer = new byte[1920];
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
while (bufferedWaveProvider.BufferedBytes > 0)
{
int bytesWritten = bufferedWaveProvider.Read(buffer, 0, buffer.Length);
if (bytesWritten == 0) break;
bool success = true;
uint bytesReturned;
success = DeviceIoControl(hDevice, IOCTL_CSMT_READ_METHOD_BUFFERED, IntPtr.Zero, 0, buffer, (uint)bytesWritten, out bytesReturned, IntPtr.Zero);
if (!success)
{
Console.WriteLine("Error sending IOCTL");
}
else
{
Console.WriteLine("Sent: " + buffer.Length);
}
// Waiting enough time to accomplish with required bitrate. If I try without this ioctl would fail all times as ring buffer get overflow.
double bytesPerMillisecond = 192;
double bytesSent = (double)bytesWritten;
double millisecondsElapsed = (double)stopwatch.ElapsedMilliseconds;
double timeToWait = (bytesSent / bytesPerMillisecond) - millisecondsElapsed;
if (timeToWait > 0)
{
Thread.Sleep((int)timeToWait);
}
stopwatch.Restart();
}
}
At the driver side I am pushing it to a ring buffer with this put method:
NTSTATUS RingBuffer::Put(BYTE* pBytes, SIZE_T count)
{
if (count > m_BufferLength) return STATUS_BUFFER_TOO_SMALL;
if (count == 0) return STATUS_SUCCESS;
if (m_Buffer == NULL) return STATUS_INVALID_DEVICE_STATE; // not initialized
NTSTATUS status = STATUS_SUCCESS;
//buffer overrun
if ((m_LinearBufferWritePosition + count) - m_LinearBufferReadPosition > m_BufferLength)
{
status = STATUS_BUFFER_OVERFLOW;
m_LinearBufferReadPosition = (m_LinearBufferWritePosition + count) - m_BufferLength + 1;
}
SIZE_T bufferOffset = m_LinearBufferWritePosition % m_BufferLength;
SIZE_T bytesWritten = 0;
while (count > 0)
{
SIZE_T runWrite = min(count, m_BufferLength - bufferOffset);
RtlCopyMemory(m_Buffer + bufferOffset, pBytes, runWrite);
bufferOffset = (bufferOffset + runWrite) % m_BufferLength;
count -= runWrite;
bytesWritten += runWrite;
}
m_LinearBufferWritePosition += bytesWritten;
if (m_IsFilling && (m_LinearBufferWritePosition - m_LinearBufferReadPosition) > (m_BufferLength / 2))
{
DPF(D_TERSE, ("RingBuffer filled with %u bytes.", (m_LinearBufferWritePosition - m_LinearBufferReadPosition)));
m_IsFilling = false;
}
return status;
}
And then it’s copied to DMA using this method:
NTSTATUS RingBuffer::Take(BYTE* pTarget, SIZE_T count, SIZE_T* readCount)
{
KeAcquireSpinLock(m_BufferLock, &m_SpinLockIrql);
if (m_IsFilling)
{
*readCount = 0;
KeReleaseSpinLock(m_BufferLock, m_SpinLockIrql);
return STATUS_DEVICE_NOT_READY;
}
count = min(count, m_LinearBufferWritePosition - m_LinearBufferReadPosition);
SIZE_T bufferOffset = m_LinearBufferReadPosition % m_BufferLength;
SIZE_T bytesRead = 0;
while (count > 0)
{
SIZE_T runWrite = min(count, m_BufferLength - bufferOffset);
RtlCopyMemory(pTarget + bytesRead, m_Buffer + bufferOffset, runWrite);
bufferOffset = (bufferOffset + runWrite) % m_BufferLength;
count -= runWrite;
bytesRead += runWrite;
}
*readCount = bytesRead;
m_LinearBufferReadPosition += bytesRead;
if (m_LinearBufferWritePosition - m_LinearBufferReadPosition == 0)
{
DPF(D_TERSE, ("RingBuffer empty with %u bytes.", (m_LinearBufferWritePosition - m_LinearBufferReadPosition)));
m_IsFilling = true;
//m_nByteAlignBufferCount = 0;
}
KeReleaseSpinLock(m_BufferLock, m_SpinLockIrql);
return STATUS_SUCCESS;
}
The buffer is initialised in this way:
//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS MiniportWaveRTStream::AllocateBufferWithNotification
(
_In_ ULONG NotificationCount_,
_In_ ULONG RequestedSize_,
_Out_ PMDL *AudioBufferMdl_,
_Out_ ULONG *ActualSize_,
_Out_ ULONG *OffsetFromFirstPage_,
_Out_ MEMORY_CACHING_TYPE *CacheType_
)
{
PAGED_CODE();
ULONG ulBufferDurationMs = 0;
if ((0 == RequestedSize_) || (RequestedSize_ < m_pWfExt->Format.nBlockAlign))
{
return STATUS_UNSUCCESSFUL;
}
if ((NotificationCount_ == 0) || (RequestedSize_ % NotificationCount_ != 0))
{
return STATUS_INVALID_PARAMETER;
}
RequestedSize_ -= RequestedSize_ % (m_pWfExt->Format.nBlockAlign);
PHYSICAL_ADDRESS highAddress;
highAddress.HighPart = 0;
highAddress.LowPart = MAXULONG;
PMDL pBufferMdl = m_pPortStream->AllocatePagesForMdl(highAddress, RequestedSize_);
if (NULL == pBufferMdl)
{
return STATUS_UNSUCCESSFUL;
}
// From MSDN:
// "Since the Windows audio stack does not support a mechanism to express memory access
// alignment requirements for buffers, audio drivers must select a caching type for mapped
// memory buffers that does not impose platform-specific alignment requirements. In other
// words, the caching type used by the audio driver for mapped memory buffers, must not make
// assumptions about the memory alignment requirements for any specific platform.
//
// This method maps the physical memory pages in the MDL into kernel-mode virtual memory.
// Typically, the miniport driver calls this method if it requires software access to the
// scatter-gather list for an audio buffer. In this case, the storage for the scatter-gather
// list must have been allocated by the IPortWaveRTStream::AllocatePagesForMdl or
// IPortWaveRTStream::AllocateContiguousPagesForMdl method.
//
// A WaveRT miniport driver should not require software access to the audio buffer itself."
//
m_pDmaBuffer = (BYTE*)m_pPortStream->MapAllocatedPages(pBufferMdl, MmCached);
m_ulNotificationsPerBuffer = NotificationCount_;
m_ulDmaBufferSize = RequestedSize_;
ulBufferDurationMs = (RequestedSize_ * 1000) / m_ulDmaMovementRate;
m_ulNotificationIntervalMs = ulBufferDurationMs / NotificationCount_;
RingBuffer::GetInstance()->Init(m_ulDmaBufferSize * 4, m_pWfExt->Format.nBlockAlign);
*AudioBufferMdl_ = pBufferMdl;
*ActualSize_ = RequestedSize_;
*OffsetFromFirstPage_ = 0;
*CacheType_ = MmCached;
return STATUS_SUCCESS;
}
As I said, it works so far and it produces sound, but it’s having a silence of few ms each X ms making the sound not continuous…
What should I do to have this in sync ?