c++ MSVAD通过IOCTL发送音频并每隔X ms获得静音

7gyucuyw  于 2023-05-20  发布在  其他
关注(0)|答案(1)|浏览(157)

我正在使用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毫秒使声音不连续..
我应该做些什么来保持同步?

krcsximq

krcsximq1#

我终于发现了这个问题,这是基于使用Thread.sleep()来定义向驱动程序发送缓冲区的时间的情况。这没有足够的精确度,在works的情况下,它通常默认为windows时钟在15- 25 ms左右,而这些毫秒在这里很重要。
实现甚至是最糟糕的,因为没有考虑到调用本身所花费的时间,使得下一个调用更晚。通常情况下,调用速度很快,但当它返回错误时,可能需要4- 5 ms。
我为这个问题添加的解决方案是使用Precission-Time.NE T来使音频发送的周期基于它,使用Windows Multimedia Timers,精度高达1 ms。

相关问题