我正在使用IOCTL将音频帧从用户区发送到内核模式驱动程序,到目前为止它还在工作,但我每隔X毫秒就会有很多静默。我正在努力与时间之间的等待发送和缓冲区大小没有运气的一分钟。
我用来发送数据的代码如下:
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();
}
}
在驱动程序端,我使用这个put方法将其推送到环形缓冲区:
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;
}
然后使用以下方法将其复制到DMA:
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;
}
缓冲区以这种方式初始化:
//=============================================================================
#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;
}
正如我所说,它的工作到目前为止,它产生的声音,但它有一个几毫秒的沉默,每X毫秒使声音不连续..
我应该做些什么来保持同步?
1条答案
按热度按时间krcsximq1#
我终于发现了这个问题,这是基于使用Thread.sleep()来定义向驱动程序发送缓冲区的时间的情况。这没有足够的精确度,在works的情况下,它通常默认为windows时钟在15- 25 ms左右,而这些毫秒在这里很重要。
实现甚至是最糟糕的,因为没有考虑到调用本身所花费的时间,使得下一个调用更晚。通常情况下,调用速度很快,但当它返回错误时,可能需要4- 5 ms。
我为这个问题添加的解决方案是使用Precission-Time.NE T来使音频发送的周期基于它,使用Windows Multimedia Timers,精度高达1 ms。