Audio low latency with Python

3.5k views Asked by At

When using pygame audio playback, I notice high latency (>100 ms):

import pygame

pygame.init()
pygame.mixer.init()
sounda = pygame.mixer.Sound("test.wav")

def callback()
    sounda.play()

# callback is called by another function, but I could measure a high latency (> 100ms)

Is pygame the cause of the latency ? More generally, is low latency audio playback possible with Python ?

Example of application : play some .wav files when MIDI messages arrive from a MIDI keyboard. (I want to code a very very basic music sampler). Of course, the latency will highly depend on the audio interface (ASIO or not ASIO, etc.), but I now want to analyse if low additionnal latency is possible with Python, and if so, which modules are preferred for this purpose.

1

There are 1 answers

0
abarnert On

Is pygame the cause of the latency ?

Probably not.

Pygame is just a wrapper around SDL. In some areas—like this one—it's a very thin wrapper.

But SDL—or, rather, SDL_mixer—could easily be the problem.

So, you'll probably need to learn a bit about SDL to use pygame for audio beyond the usual gaming-style needs. Audio with SDL is a nice overview, although it seems a bit out of date.


The first thing to consider is what audio driver you're using. For example, on many linux systems, ALSA can't do low-latency sound, which means that anything you write that ultimately talks to ALSA can't do it either. And if your system is set to use esd or some other sound daemon if possible and fall back if necessary, you obviously don't want that here. So, if something like that is your problem, you will have to configure SDL_mixer to use a different driver.


Assuming the driver can handle it, it definitely is possible to do low-latency sound with pygame.mixer/SDL_mixer. But it may not work out of the box.

The first thing you'll want to do is pick a smaller buffer size than the default.

Also note that SDL_mixer will re-encode your sounds automatically behind your back if they're not in the same sample rate/etc. as the target, which not only adds a bit of latency for the CPU work, it also means the real buffer size has nothing to do with the one you think you're using…

An alternative to this is to go around pygame.mixer/SDL_mixer, do the mixing yourself, and go right to pygame.sound/SDL_sound. That will still have the same driver issues, but anything that's caused by SDL_mixer (like re-encoding) goes away.

If you can't get pygame/SDL to do what you want (e.g., because the only drivers it supports on your system all suck), you will have to use a different library. PythonInMusic on the wiki has hundreds of links, and you can also search PyPI. However, you might want to start from the other side—find a C audio library you want to use, then search for Python bindings for it. For example, pyAudio is a relatively thin wrapper around PortAudio, so it rocks if PortAudio's portability, configurability, and performance requirements meet your needs and its API fits your design, but it sucks otherwise.


Another place things can go wrong is in your code.

That's obviously not the issue in your case, because all you're doing is giving pygame.mixer a pre-made sound. But if you decide you need to, say, pre-convert the sounds and feed buffers into pygame.sound, you may run into the issue that Python is slow at looping and slow at arithmetic.

By "slow" I mean on the order of microseconds. Looping once per 20ms buffer is not a problem. Looping once per sample might be. If you're doing any processing, you should consider using NumPy or a dedicated audio library to do the grunt work rather than pure Python.