Newbie Asio Playback Question

Oct 19, 2012 at 6:48 PM
Edited Oct 19, 2012 at 6:49 PM

Hi Mark,

I just wanted to say thanks again for sharing NAudio. I'm new to programming, and I'm doing it as a hobby, and to learn some stuff. I really don't know that much about c# yet, but I do see that it's similar to visual basic.

From studying the demo you have, and looking at the tutorials that giawa has, I've managed to put together a test using VB.net

I can open, and play a wav file, and select my asio driver. I'm using a Lynx Studio AES 16 sound card (buffer is set 512).

When I hit stop, it stutters for a few ms. I know I'm missing something, but I can't figure out what. Am I not disposing the asio driver correctly?

Any help would greatly be appreciated.

Many Thanks, Wyatt

Here's my code:

 

Imports NAudio.Wave
Public Class Form1
    Private wave As NAudio.Wave.WaveFileReader = Nothing
    Private output As NAudio.Wave.AsioOut = Nothing

    Public Sub New()
        InitializeComponent()
        InitialiseAsioControls()
    End Sub

    Public ReadOnly Property SelectedDeviceName() As String
        Get
            Return CStr(comboBoxAsioDriver.SelectedItem)
        End Get
    End Property

    Private Sub InitialiseAsioControls()
        ' Just fill the comboBox AsioDriver with available driver names
        Dim asioDriverNames = AsioOut.GetDriverNames()
        For Each driverName As String In asioDriverNames
            comboBoxAsioDriver.Items.Add(driverName)
        Next driverName
        comboBoxAsioDriver.SelectedIndex = 0
    End Sub

    Private Sub btnOpen_Click(sender As System.Object, e As System.EventArgs) Handles btnOpen.Click
        Dim wavFile As String
        OpenWavFile.InitialDirectory = ""
        wavFile = OpenWavFile.ShowDialog()
        wavFile = OpenWavFile.FileName
        Label1.Text = wavFile
    End Sub

    Private Sub btnPlay_Click(sender As System.Object, e As System.EventArgs) Handles btnPlay.Click
        If Label1.Text = Nothing Then
            Dim wavFile As String
            OpenWavFile.InitialDirectory = ""
            wavFile = OpenWavFile.ShowDialog()
            wavFile = OpenWavFile.FileName
            Label1.Text = wavFile
        Else
            wave = New NAudio.Wave.WaveFileReader(Label1.Text)
            output = New AsioOut(comboBoxAsioDriver.Text)
            output.Init(New NAudio.Wave.WaveChannel32(wave))
            output.Play()
            btnPlay.Enabled = False
        End If
    End Sub

    Private Sub DisposeWave()
        If output IsNot Nothing Then
            If output.PlaybackState = NAudio.Wave.PlaybackState.Playing Then
                output.Stop()
            End If
            output.Dispose()
            output = Nothing
        End If
        If wave IsNot Nothing Then
            wave.Dispose()
            wave = Nothing
        End If
    End Sub

    Private Sub btnStop_Click(sender As System.Object, e As System.EventArgs) Handles btnStop.Click
        DisposeWave()
        btnPlay.Enabled = True
    End Sub
End Class

Coordinator
Oct 19, 2012 at 7:50 PM

don't Dispose straight away after stop. Call output.Stop(), and then in the handler for PlaybackStopped, then you can Dispose the device. Also, please make sure you are using the very latest NAudio (there is a pre-release build up on NuGet)

Oct 19, 2012 at 9:15 PM

Thanks for the quick reply. Sorry for being a noob.

Could you give me an example in the test code I have above, or do you mean where I have btnStop

add output.stop() first? I tried this, and it still stutters. I'm still not understanding something.

    Private Sub DisposeWave()
        If output IsNot Nothing Then
            If output.PlaybackState = NAudio.Wave.PlaybackState.Playing Then
                output.Stop()
            End If
            output.Dispose()
            output = Nothing
        End If
        If wave IsNot Nothing Then
            wave.Dispose()
            wave = Nothing
        End If

    End Sub

    Private Sub btnStop_Click(sender As System.Object, e As System.EventArgs) Handles btnStop.Click
        output.Stop()
        DisposeWave()
        btnPlay.Enabled = True
    End Sub

Coordinator
Oct 19, 2012 at 9:19 PM

get rid of the call to output.Dispose(). The ASIO driver is not ready for it yet.

Instead, subscribe to output.PlaybackStopped event. Then in there, call output.Dispose().

Alternatively, only Dispose the device when you press play again or close your form

Oct 19, 2012 at 10:53 PM

Thanks again Mark.

In the code above, I got rid of output.Dispose()

I still don't understand "Instead, subscribe to output.PlaybackStopped event."

Oct 19, 2012 at 11:02 PM

In addition to what Mark said, I think you will need to keep a reference to the WaveChannel32 object, so you can set .PadWithZeros , which enables the PlaybackStopped event.

Something like this:

Private WithEvents output As NAudio.Wave.AsioOut = Nothing
Private mainOutputStream As WaveChannel32
... 
mainOutputStream = New WaveChannel32(wave) ' tie the file reader to the output stream 
mainOutputStream.PadWithZeroes = False ' need this to detect eof
output.Init(mainOutputStream) ' tie output stream to output device
...

Private Sub PlaybackStopped(sender As Object, e As System.EventArgs) Handles output.PlaybackStopped
        ' dispose
End Sub

Oct 20, 2012 at 4:43 PM

Thanks again for your help Mark, and eejake52.

Looks like I need to start over, or something. I never did get it to work right. No matter what I've tried, it still stutters when I hit the stop button.

I'll still keep trying.

Thanks, Wyatt

Coordinator
Oct 21, 2012 at 4:21 PM

If you use the NAudioDemo application, can you play and stop ASIO without it stuttering?

Oct 22, 2012 at 9:46 PM
Edited Oct 22, 2012 at 9:50 PM

Hi Mark.

Yes, when I load the demo "AsioDirectDemo" and stop, there is no stutter. I used the code from that demo in a new test. and converted that into vb.net, but it stutters.

Sorry again for be such a noob at this. Maybe it's something I'm not converting right?

Here's the converted code from that panel. I put the panel controls on a form, and used the converted code:

 

Imports NAudio.Wave

Public Class Form1
    Private reader As WaveFileReader
    Private asioOut As AsioOut
    Public Sub New()
        InitializeComponent()
        AddHandler Disposed, AddressOf AsioDirectPanel_Disposed
        For Each device In AsioOut.GetDriverNames()
            Me.comboBoxAsioDevice.Items.Add(device)
        Next device
        If Me.comboBoxAsioDevice.Items.Count > 0 Then
            Me.comboBoxAsioDevice.SelectedIndex = 0
        End If
    End Sub

    Private Sub AsioDirectPanel_Disposed(ByVal sender As Object, ByVal e As EventArgs)
        Cleanup()
    End Sub

    Private Sub Cleanup()
        If Me.asioOut IsNot Nothing Then
            Me.asioOut.Dispose()
            Me.asioOut = Nothing
        End If
        If Me.reader IsNot Nothing Then
            Me.reader.Dispose()
            Me.reader = Nothing
        End If
    End Sub

    Private Sub buttonSelectFile_Click_1(sender As System.Object, e As System.EventArgs) Handles buttonSelectFile.Click
        Cleanup()
        Dim ofd As New OpenFileDialog()
        ofd.Filter = "WAV files|*.wav"
        If ofd.ShowDialog() = DialogResult.OK Then
            Me.reader = New WaveFileReader(ofd.FileName)
        End If
    End Sub

    Private Sub buttonPlay_Click(ByVal sender As Object, ByVal args As EventArgs) Handles buttonPlay.Click
        Try
            Play()
        Catch e As Exception
            MessageBox.Show(e.Message)
        End Try
    End Sub

    Private Function GetUserSpecifiedChannelOffset() As Integer
        Dim channelOffset As Integer = 0
        Integer.TryParse(textBoxChannelOffset.Text, channelOffset)
        Return channelOffset
    End Function

    Private Sub Play()
        ' allow change device
        If Me.asioOut IsNot Nothing AndAlso (Me.asioOut.DriverName <> comboBoxAsioDevice.Text OrElse Me.asioOut.ChannelOffset <> GetUserSpecifiedChannelOffset()) Then
            Me.asioOut.Dispose()
            Me.asioOut = Nothing
        End If

        ' create device if necessary
        If Me.asioOut Is Nothing Then
            Me.asioOut = New AsioOut(comboBoxAsioDevice.Text)
            Me.asioOut.ChannelOffset = GetUserSpecifiedChannelOffset()
            Me.asioOut.Init(Me.reader)
        End If

        Me.reader.Position = 0
        Me.asioOut.Play()
        Me.Timer1.Enabled = True
        SetButtonStates()
    End Sub

    Private Sub SetButtonStates()
        buttonPlay.Enabled = asioOut IsNot Nothing AndAlso asioOut.PlaybackState <> PlaybackState.Playing
        buttonStop.Enabled = asioOut IsNot Nothing AndAlso asioOut.PlaybackState = PlaybackState.Playing
    End Sub

    Private Sub buttonStop_Click(ByVal sender As Object, ByVal e As EventArgs) Handles buttonStop.Click
        Me.Stop()
    End Sub

    Private Sub [Stop]()
        Me.asioOut.Stop()
        Me.Timer1.Enabled = False
        SetButtonStates()
    End Sub

    Private Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Timer1.Tick
        If asioOut IsNot Nothing AndAlso asioOut.PlaybackState = PlaybackState.Playing AndAlso reader.Position >= reader.Length Then
            Me.Stop()
        End If
    End Sub
End Class
Coordinator
Oct 23, 2012 at 6:23 AM

that's strange. I'm not a VB.NET developer but I cans see anything obviously wrong there. Could it be the garbage collector kicking in? ASIO works at a very low latency, and so if the garbage collector kicks in at an inopportune moment, it can cause stuttering and there is nothing much that can be done about it.

Oct 23, 2012 at 6:53 PM

Hi Mark.

Thanks again for your patience on this.

I don't think that's the problem. I wish I had an example of just the Asio Direct Playback on a windows form. I'm having trouble with the panel.

I tried to do this on a windows form in c# (basically I copied and pasted the demo code), and it builds ok, and it works, but still stutters on stop.

This has me thinking that I'm definitely doing something wrong when I can build, and play the demo that you have with no stuttering, but when I use the same code on a form instead of a panel, it does. Hope this make sense.

Anyway, here's a link to my solution file. If you have a chance, could you take a look at it?

Thanks, Wyatt

 

Coordinator
Oct 24, 2012 at 6:14 AM

Hi Wyatt,

I've tried your app and I can hear the "stutter" you refer to. There is also some noise when I start playing again. But the same effect is in the NAudioDemo app for me, so its clearly nothing you are doing wrong. I guess there may be a bug in the way the ASIO stop code is working and perhaps some uninitialised buffers get to play out. I'll see if I can find some time to investigate.

Mark

Oct 24, 2012 at 4:10 PM

Thanks Mark.

I just checked the new version of NAudio from the source in which you guys were having a discussion on "Noise in playback start" It seems to be working correctly now. There's no stuttering now, and I also tested it out in the VB test project I had started, and it's working as well.

Thanks, Wyatt

Oct 24, 2012 at 4:25 PM

Edit:

I just checked again. It still stutters when I'm in visual studio, and build, and playback from there. When I close visual studio, and goto my bin debug folder, and open the app from there, it doesn't stutter.

Coordinator
Oct 24, 2012 at 4:33 PM

the fix I made shouldn't affect anything to do with stopping. It was fixing a problem where you heard a bit of leftover MP3 from within the decoders buffer when you started playing again. The stutter is a strange one, and might still be related to garbage collections. The ASIO API is pretty simple - you just call driver.Stop(), so it's hard to think what else could be done to fix the problem.