How to determine latency?

Apr 13, 2011 at 7:04 PM
Edited Apr 14, 2011 at 12:40 PM

I am using a slightly modified code block from PracticeSharp that applies effects to the wave provider.
The CurrentTime property of the wave provider always seems to be 200ms + later than what I am hearing.
The latency increases as I set the Direct Sound latency value higher.
I can raise an event with the current play time, but I cannot trust the value I am getting from the Wave provider.
Subscribing to the PlayPositionChanged event of the wave provider also gives me the "later than I am hearing" value.
Anyone have a tip on how I can get the true time value of what I am hearing?
I also noticed that the event only fires 10 times a second and I need it to fire every 33ms.
What do i need to set so that smaller blocks of data are being processed and the event gets raised more frequently?
I have tried

((bytesRead / format.Channels) / format.SampleRate) * 1000
to get an offset in milliseconds, but I would think that the WaveProvider could do this since it has all of the information that I am using.

Here is the code block:

  Private Sub ProcessAudio()
    m_stopWorker = False
    m_workerRunning = True

    Try
      Dim format As WaveFormat = m_waveChannel.WaveFormat
      Dim bufferSecondLength As Integer = format.SampleRate * format.Channels
      Dim inputBuffer As Byte() = New Byte(BufferSamples * 4 - 1) {}

      Dim convertInputBuffer As New ByteAndFloatsConverter()
      convertInputBuffer.Bytes = inputBuffer

      Dim outBufferSizeFloats As UInteger = CUInt(convertInputBuffer.Bytes.Length) \ CUInt(4 * format.Channels)

      Dim bytesRead As Integer
      Dim floatsRead As Integer
      Dim samplesProcessed As UInteger = 0
      Dim bufferIndex As Integer = 0
      Dim actualEndMarker As TimeSpan = TimeSpan.Zero

      'AddHandler m_inputProvider.PlayPositionChanged, AddressOf inputProvider_PlayPositionChanged

      While Not m_stopWorker AndAlso m_waveChannel.Position < m_waveChannel.Length
        SyncLock PropertiesLock
          m_waveChannel.Volume = m_volume
        End SyncLock

        'Read samples from file

        Dim myEffectStartTime As DateTime = Now

        ' Change current play position
        SyncLock CurrentPlayTimeLock
          If m_newPlayTimeRequested Then
            m_waveChannel.CurrentTime = m_newPlayTime
            m_newPlayTimeRequested = False
          End If
        End SyncLock

        ' *** Read one chunk from input file ***
        bytesRead = m_waveChannel.Read(convertInputBuffer.Bytes, 0, convertInputBuffer.Bytes.Length)
        ' **************************************

        floatsRead = bytesRead \ ((4) * format.Channels)
        samplesProcessed = floatsRead

        ApplyPitchShiftEffect(convertInputBuffer.Floats, floatsRead)

        Dim currentBufferTime As TimeSpan = m_waveChannel.CurrentTime

        m_inputProvider.AddSamples(convertInputBuffer.Bytes, 0, convertInputBuffer.Bytes.Length, currentBufferTime)

        While Not m_stopWorker AndAlso m_inputProvider.GetQueueCount() > BusyQueuedBuffersThreshold
          Thread.Sleep(10)
          SyncLock CurrentPlayTimeLock
            m_currentPlayTime = m_waveChannel.CurrentTime
          End SyncLock
        End While

        m_effectLatency = Now.Subtract(myEffectStartTime)

        Dim actualTime As TimeSpan = m_currentPlayTime.Subtract(m_effectLatency)

        RaiseEvent PlayTimeChanged(Me, New BufferedPlayEventArgs(actualTime))

      End While

      ' Stop listening to PlayPositionChanged events
      'RemoveHandler m_inputProvider.PlayPositionChanged, AddressOf inputProvider_PlayPositionChanged

      ' Fix to current play time not finishing up at end marker (Wave channel uses positions)
      If Not m_stopWorker AndAlso CurrentPlayTime < actualEndMarker Then
        SyncLock CurrentPlayTimeLock
          m_currentPlayTime = actualEndMarker
        End SyncLock
      End If

      '#End Region
      ChangeStatus(Statuses.Stopped)
    Finally
      m_workerRunning = False
    End Try
  End Sub
Coordinator
Apr 14, 2011 at 8:11 PM

I'm not sure what is firing the PlayPositionChanged event - this must be a feature of Practice Sharp, and you should be able to configure it to fire as often as you need. It might be worth asking a question there. It is quite hard to get an accurate handle on exactly what is being played as audio software typically renders ahead of time and then passes buffers to the sound card. The smaller those buffers (i.e. lower latency), the more accurate a picture of current position you will get, but lower latency is more processor intensive

Mark

Apr 15, 2011 at 12:59 PM

Thanks for the reply, I'll dig deeper into seeing exactly what is going on in my code now that I am clear on the strategy.

Do you have a small snippet available that demonstrates how you intended effects to be inserted from file load to play stopped?

Is this the intended workflow?

waveChannel.read ----> ApplyEffect ---> InputProvider.addSamples 

Would it be possible to make the WaveChannel or any other input/provider have an effect chain : List(of IEffect) and have the IEffect/IEffectChain interface be part of NAudio?

 

 

Coordinator
Apr 15, 2011 at 1:27 PM

I implemented an effect chain as part of the Skype Voice Changer project, and my intention has always been to eventually move it into NAudio. It is quite easy to augment NAudio with your own custom one though.

I would usually insert effects after going to floating point as it makes writing the DSP code much easier

Mark