psychopy.org | Reference | Downloads | Github

Low sound latency with sounddevice module & wdm-ks driver

#1

Hello,

just wanted to let you know, that I get very low sound latencies (as measured with an oscilloscope) of 1-1.5 ms, when using wdm-ks drivers with the sounddevice module (https://github.com/spatialaudio/python-sounddevice/)

n = 42, mean = 1.2 ms, sd = 0.10 ms, min = 1,05 ms, max = 1.53

It is a huge gain. I tried out pyo with wdm-ks drivers as well, but had latencies of 50ms. My best latencies with pyo were, when using asio4all with sample rate of 96000 and buffer size of 32; mean latency of 30 ms, jitter of 10 ms however…

I don’t know, what the developer of sounddevice made different, but I’m very very happy about it.

It seems to be finally a robust solution for our sound card on windows.

Best wishes
Piotr

2 Likes
#2

Sounds good if that’s working for you. I’d love to see something working well especially something that’s easier and more robust than pyo. And this supports Python3 which is a bonus too (pyo is one of the libs holding us back to py2)

I can’t see the way that sounddevice accesses the wdm drivers - it looks like it simply uses portaudio again - makes it more surprising that the performance is better again.

#3

yes, it uses portaudio; I thought, that the reason might be that portaudio is compiled in a different way (the portaudio.dll, which is bandled with sounddevice is quite smaller than the one with pyo), but I tried out pyo with this dll and there was a latency of 50ms as well, so it doesn’t have to do anything with the way portaudio was compiled, I think.

sounddevice seems just to work. The sound is accessed as numpy arrays so it’s easy to increase the volume as well.

By the way I’ve also tried out the portaudio.dll (with asio), which is compiled by
the guys from psychtoolbox with some small changes, but the performance was basically
the same. So I have tried out quite some things and this was the best option.

I thought about using pyaudio originally and I’ve compiled portaudio with wdm-ks etc. for this purpose, but wasn’t able to compile pyaudio itself. Then I came accross sounddevice and I’m still amazed by its good performance on Win7.

2016-08-22 19:09 GMT+02:00 Jon Peirce <psychopy@discoursemail.com>:

jon

August 22

Sounds good if that's working for you. I'd love to see something working well especially something that's easier and more robust than pyo. And this supports Python3 which is a bonus too (pyo is one of the libs holding us back to py2)

I can't see the way that sounddevice accesses the wdm drivers - it looks like it simply uses portaudio again - makes it more surprising that the performance is better again.


Visit Topic or reply to this email to respond.

To unsubscribe from these emails, click here.

1 Like
#4

I’m the author of the sounddevice module and I wanted to say hi and confirm and clarify a few points.

It indeed uses only PortAudio and there is nothing special done for any audio backend. Especially the Windows backends receive no special treatment, since I don’t even have a Windows computer.

I published the bundled DLLs in a separate repository, and they were cross-compiled on Linux using mxe. I myself tested them only very shortly on a VirtualBox image of Windows 10, but according to some feedback from users, they seem to work fine on “normal” Windowses, too. If you put a DLL named portaudio.dll somewhere onto the Python path, it will be used instead of the bundled DLL. This may happen accidentally if you have pyo installed (see issue 36).

I didn’t do anything special to get lower latency than other PortAudio wrappers. In fact, I’m surprised about that, since the sounddevice module isn’t an extension module written in C, instead it is pure Python and uses CFFI in ABI/in-line mode to access the PortAudio API.

How did you use the sounddevice module when measuring the latency?

I wouldn’t recommend to use the play() function in your case, because it each time creates, opens and closes a new OutputStream object. On some (cheap) soundcards, opening and closing the stream may cause clicks, which would obviously be bad for your use case. This also uses a callback function written in Python, which may be less reliable due to the GIL and the GC.

The OutputStream.write() method just passes the data to PortAudio, which might be better. But probably it’s not, I didn’t measure it …
However, there is no way to control the exact time when the sound starts, so there might be more jitter than necessary, especially when using large block sizes (but again, I don’t actually know, I’m just guessing).
I don’t know if the jitter of the above measurements (maximum 0.33 ms) is to be considered good or bad.

If you really want to get into it, you could try to write your own wrapper using CFFI’s API/out-of-line mode. This way you could implement your own callback function in C, but it also would mean that you have to have a C compiler to create your extension module (which wouldn’t be a problem since you ship binary packages anyway).
When you write your own callback function, you could try to reduce jitter at the expense of some additional latency.

Using CFFI is really simple, so if you have very special requirements, it might be better for you to skip all the PortAudio wrappers and just write your own code directly accessing the PortAudio API.

2 Likes
#5

I just used the play function, when measuring sound latency with sounddevice. I’ll try it out with the OutputStream.write() method. I attached an oscilloscope to the computer and measured the delay between the sound onset and the trigger coming from the parallel port.

Well, there are actually not many PortAudio wrappers for python, which support wdm-ks out of the box. Olivier from pyo compiled it for testing purposes, when I asked him about sound latency issues. From what I’ve read so far most windows drivers are just terrible, when it comes to sound latency.

I had some issues with the wdm-ks drivers as well, however. I tested the latency on a Windows8 computer with a cheap onboad sound card a few days ago. The delay was also low (around 1ms), but only 40ms of the 50ms sound were actually played back. 10ms were missing each time, when the sound was played. I should try it out with the OutputStream.write() method one time.

#6

mhm… OutputStream.write() works with wasapi, but it doesn’t work with wdm-ks;

sounddevice.PortAudioError: Error opening OutputStream: Unanticipated host API 4
error -9999: u’Blocking API not supported yet’

what would you recommend to play back the sound in that case?

I just tested OutputStream.write() on Win8 with the onboard sound card; when using OutputStream.write() with wasapi the full sound (50ms) is played back (40ms are played back, when using sd.play)

#7

I’ve also seen the problem that the beginning and/or the end of the sound are not played. I don’t know if that’s a PortAudio problem or a problem of the underlying audio drivers. This is yet another reason to start the stream in the beginning of the session, some time before the actual audio signals are to be played back.

According to the PortAudio docs, the blocking API is not supported on all host APIs, it looks like WDM-KS is one of those. I don’t know if this is supported in a more recent development version of PortAudio?

I’d recommend that you write your own callback function. In the beginning, it would only write zeros to the output. As soon as an audio signal is to be played back, this function should write blocks of it to the output.

Once that works, you could use the time attribute to start playback at a fixed delay. This way you will add some latency, but if all goes well, it should reduce the jitter.

If that actually works, you could put in the extra effort to re-write the callback function in C and compile it with CFFI in API/out-of-line mode. This should give you the most reliable performance, since Python wouldn’t be involved at all in the callback.

What would it take to replace pyo?
#8

thanks, Matthias. I’ll try to do this. Actually we are playing back only white noise, so I even wouldn’t need to open a wave file, but just create the white noise in the callback function of portaudio in C, I think… that would be probably the most elegant solution

2016-08-28 18:28 GMT+02:00 matthias.geier <psychopy@discoursemail.com>:

matthias.geier

August 28

I've also seen the problem that the beginning and/or the end of the sound are not played. I don't know if that's a PortAudio problem or a problem of the underlying audio drivers. This is yet another reason to start the stream in the beginning of the session, some time before the actual audio signals are to be played back.

According to the PortAudio docs, the blocking API is not supported on all host APIs, it looks like WDM-KS is one of those. I don't know if this is supported in a more recent development version of PortAudio?

I'd recommend that you write your own callback function. In the beginning, it would only write zeros to the output. As soon as an audio signal is to be played back, this function should write blocks of it to the output.

Once that works, you could use the time attribute to start playback at a fixed delay. This way you will add some latency, but if all goes well, it should reduce the jitter.

If that actually works, you could put in the extra effort to re-write the callback function in C and compile it with CFFI in API/out-of-line mode. This should give you the most reliable performance, since Python wouldn't be involved at all in the callback.


Visit Topic or reply to this email to respond.


In Reply To

piotr

August 26mhm… OutputStream.write() works with wasapi, but it doesn’t work with wdm-ks;

sounddevice.PortAudioError: Error opening OutputStream: Unanticipated host API 4 error -9999: u’Blocking API not supported yet’

what would you recommend to play back the sound in that case?

I just tested OutputStre…


Visit Topic or reply to this email to respond.

To unsubscribe from these emails, click here.

#9

I’ve made some progress implementing a sounddevice backend for psychopy and it looks good. Let’s close this thread and move discussion to here:

closed #10