[wdmaudiodev] Re: Need help in Passing a test case from KS-Position test

  • From: Matthew van Eerde <Matthew.van.Eerde@xxxxxxxxxxxxx>
  • To: "wdmaudiodev@xxxxxxxxxxxxx" <wdmaudiodev@xxxxxxxxxxxxx>
  • Date: Fri, 23 May 2014 00:14:48 +0000

OK, here's what I came up with.

Features:


1)      A TODO for the driver developer who is using this as a template, to 
remind them to hook up the clock to the layer below them

2)      No artificially introduced jitter at all (not even 0.29 ms)

3)      No random slop introduced by rounding

4)      An arbitrary startup delay controlled by m_ullStartupDelayRemaining, 
which I set to 8 * HNS_PER_MILLISECOND in the transition to KSSTATE_RUN; this 
makes MSVad have a measurable delay between SetState(KSSTATE_RUN) and the first 
non-zero device position

//=============================================================================
STDMETHODIMP
CMiniportWaveCyclicStreamMSVAD::GetPosition
(
    _Out_ PULONG                  Position
)
/*++

Routine Description:

  The GetPosition function gets the current position of the DMA read or write
  pointer for the stream. Callers of GetPosition should run at
  IRQL <= DISPATCH_LEVEL.

Arguments:

  Position - Position of the DMA pointer

Return Value:

  NT status code.

--*/
{
    if (m_fDmaActive)
    {
        // TODO: Query the physical layer below us and
        // report the byte position of the sample,
        // either emerging from the speaker (if this is render)
        // or coming in to the microphone (if this is capture)
        //
        // Alas, WaveCyclic does not allow us to report a correlated position + 
QPC,
        // just a position.
        // So if the layer below us gives us a correlated position + QPC,
        // we have to do our own extrapolation and hand the extrapolated 
position to portcls
        //
        // Since this is only a virtual driver, we use KeQueryInterruptTime as 
a clock
        //
        ULONGLONG CurrentTime = KeQueryInterruptTime();
        ULONGLONG TimeElapsed = CurrentTime - m_ullDmaTimeStamp;

        // Simulate a startup delay
        //
        if (m_ullStartupDelayRemaining > 0)
        {
            // burn down some startup delay
            if (TimeElapsed > m_ullStartupDelayRemaining)
            {
                TimeElapsed -= m_ullStartupDelayRemaining;
                m_ullStartupDelayRemaining = 0;
            }
            else
            {
                m_ullStartupDelayRemaining -= TimeElapsed;
                TimeElapsed = 0;
            }
        }

        // We have an elapsed time in hundred-nanoseconds, which we'll call 
"ticks"
        // but we want to convert it into a DMA position, which is in bytes
        //
        // Let H = 10 * 1000 * 1000;
        // this is the number of ticks which pass in a second
        //
        // Let B = m_ulDmaMovementRate = pwfx->nAvgBytesPerSec;
        // this is the number of bytes processed in a second
        //
        // Let h = ullElapsed;
        // this is the number of ticks that have elapsed
        //
        // We want to know b = the number of bytes that been processed
        //
        // b / B = h / H = the number of seconds that have elapsed
        // Solving for b, we get: b = B h / H
        //
        // In particular, floor(B h / H) bytes have completely been processed,
        // and we are (B h mod H) / H of the way into the next byte (this is 
between 0 and 1)
        //
        // To avoid dealing with floating-point arithmetic:
        // Let br = the byte rollover (B h mod H) which is a number in [0, H)
        //
        // But wait!
        // We need to consider the possibility that we had nonzero rollover 
from before;
        // "b = B h / H" was too naive, it should have been "b = (B h + 
br_prev) / H"
        //
        // In summary:
        // H = 10 * 1000 * 1000 = the number of ticks in a second
        // B = the number of bytes processed in a second
        // h = the number of ticks that have passed since the last time
        // br_prev = how far into the current byte we already were the last 
time, in [0, H)
        //
        // So we calculate:
        // b = floor((B h + br_prev) / H) = how many bytes have no completely 
passed
        // br_next = (B h + br_prev) mod H
        //
        ULONG ulByteDisplacement =
            (ULONG)( (m_ulDmaMovementRate * TimeElapsed + m_ullDmaPartialByte) 
/ HNS_PER_SECOND );

        m_ullDmaPartialByte =
            (m_ulDmaMovementRate * TimeElapsed + m_ullDmaPartialByte) % 
HNS_PER_SECOND;

        // Now we have the number of bytes that have been newly processed
        // Advance the position by that much, relative to the circular buffer
        //
        m_ulDmaPosition = (m_ulDmaPosition + ulByteDisplacement) % 
m_ulDmaBufferSize;

        // Return the new DMA position
        //
        *Position = m_ulDmaPosition;

        // Update the DMA time stamp for the next call to GetPosition()
        //
        m_ullDmaTimeStamp = CurrentTime;
    }
    else
    {
        // DMA is inactive so just return the current DMA position.
        //
        *Position = m_ulDmaPosition;
    }

    return STATUS_SUCCESS;
} // GetPosition

Other related posts: