python-sounddevice icon indicating copy to clipboard operation
python-sounddevice copied to clipboard

Multiprocessing?

Open jrbrodie77 opened this issue 5 years ago • 2 comments

When I run sd.play in a flask app or ipython terminal, I get some sonic artifacts when there is additional load on Python. Have you tried running SD in multiprocessing, any advice on how to do that?

jrbrodie77 avatar Jan 29 '21 02:01 jrbrodie77

FWIW, I hammered out a test class in multiprocessing. I haven't tested it on my other machine yet, so I can't say if it helped with the blips and bops. For those who need MP, it might help:


import numpy as np
from multiprocessing import Process, Queue


fs = 48000
rand_data = np.random.rand(48000 * 5) * 0.1


class AudioMan(Process):
    """Audio Manager
    The multiprocessing.Process class causes two
    instances of the class to exist. The first is in the parent
    process. When .start is called, a second process and instance
    are created. Sounddevice is imported in .run to avoid importing is
    twice, which causes some kind of PortAudio crash."""

    def __init__(self):
        self.state = 'stopped' # Set to quit to exit.
        self.q = Queue()
        self.sd = None
        super(AudioMan, self).__init__()

    def run(self):
        # Sounddevice can't handle being imported in multiple processes.
        # importing it in `run` causes it to only be imported in the
        # spawned process.
        import sounddevice as sd
        self.sd = sd
        while self.state != 'quit':
            cmd = self.q.get()
            if cmd == 'quit':
                self.state = 'quit'
            else:
                # Example: pass ('play', 'foo.wav')
                if isinstance(cmd, tuple):
                    method, args = cmd
                    getattr(self, method)(args)

    def play(self):
        self.q.put(('_play', 'foo.wav'))

    def _play(self, fn):
        self.sd.play(rand_data, 48000, device=1)
        print(fn)

    def stop(self):
        self.q.put(('_stop', None))

    def _stop(self, _):
        self.sd.stop()

    def quit(self):
        self.q.put('quit')

jrbrodie77 avatar Jan 29 '21 03:01 jrbrodie77

I don't really have any experience with multiprocessing, but I would have suggested what you have already done in your code example:

  • import sounddevice only in one process
  • communicate with a queue

In general, I would advise to avoid multiprocessing unless it is absolutely necessary.

There are already a few issues related to multiprocessing: #120, #147, #154, #302, ...

I get some sonic artifacts when there is additional load on Python.

Did you manage to reduce/avoid those with multiprocessing?

Another possible approach would be to use a callback function implemented in C (or another compiled language) and make sure that the callback function never takes the GIL. I've experimented with that approach at https://github.com/spatialaudio/python-rtmixer. A similar thing might even work with Cython, but I've not tried this.

mgeier avatar Jan 30 '21 18:01 mgeier