spatialaudio / python-sounddevice

:sound: Play and Record Sound with Python :snake:
https://python-sounddevice.readthedocs.io/
MIT License
1.02k stars 149 forks source link

Eventlet interaction? #330

Open natankeddem opened 3 years ago

natankeddem commented 3 years ago

Hello, I am getting a hang in the audio playback\recording (need to CTRL-C) with playrec() when I monkey_patch with eventlet. Running RPI4 on Raspberry Pi OS 5.10 utilizing I2S codec device. Code works fine without the monkey_patch.

Example:

import eventlet

eventlet.monkey_patch()
import numpy as np
import sounddevice as sd

def pr():
    z = np.zeros(48000)
    sd.default.device = 2
    rec = sd.playrec(z, 48000, channels=2, blocking=True)
    print(rec)

if __name__ == "__main__":
    pr()

Trace on CTRL-C:

(venv) pi@raspberrypi:~/ProdTestSys0 $ /home/pi/py0/venv/bin/python /home/pi/py0/sc0.py
^CTraceback (most recent call last):
  File "/home/pi/py0/sc0.py", line 16, in <module>
    pr()
  File "/home/pi/py0/sc0.py", line 11, in pr
    rec = sd.playrec(z, 48000, channels=2, blocking=True)
  File "/home/pi/py0/venv/lib/python3.7/site-packages/sounddevice.py", line 371, in playrec
    **kwargs)
  File "/home/pi/py0/venv/lib/python3.7/site-packages/sounddevice.py", line 2583, in start_stream
    self.wait()
  File "/home/pi/py0/venv/lib/python3.7/site-packages/sounddevice.py", line 2592, in wait
    self.event.wait()
  File "/usr/lib/python3.7/threading.py", line 552, in wait
    signaled = self._cond.wait(timeout)
  File "/usr/lib/python3.7/threading.py", line 296, in wait
    waiter.acquire()
  File "/home/pi/py0/venv/lib/python3.7/site-packages/eventlet/semaphore.py", line 120, in acquire
    hubs.get_hub().switch()
  File "/home/pi/py0/venv/lib/python3.7/site-packages/eventlet/hubs/hub.py", line 313, in switch
    return self.greenlet.switch()
  File "/home/pi/py0/venv/lib/python3.7/site-packages/eventlet/hubs/hub.py", line 365, in run
    self.wait(sleep_time)
  File "/home/pi/py0/venv/lib/python3.7/site-packages/eventlet/hubs/poll.py", line 77, in wait
    time.sleep(seconds)
KeyboardInterrupt
mgeier commented 3 years ago

Thanks for the report!

The problem seems to be that this line never finishes:

https://github.com/spatialaudio/python-sounddevice/blob/d353fbf6c751a7a91169c614df8f905f2c55821c/sounddevice.py#L2598

... even though this line is called:

https://github.com/spatialaudio/python-sounddevice/blob/d353fbf6c751a7a91169c614df8f905f2c55821c/sounddevice.py#L2568

So it looks like eventlet.monkey_patch() somehow messes with threading.Event. But this seems to only happen with the thread created by PortAudio, there's no problem when using "normal" Python threads.

Other than that, I don't know what exactly is going wrong nor how to fix it ...

Could you please create an issue at the eventlet project, maybe they can help?

There is a similar-sounding issue: https://github.com/eventlet/eventlet/issues/395 There it is suggested to use eventlet.monkey_patch(thread=False) which seems to make your example work!

natankeddem commented 3 years ago

I will put up something over at the Eventlet issues page. I am not sure of the ramifications of using thread=False; this code is being used in a larger code base which utilizes Flask & Flask-SocketIO which would likely not appreciate that change I imagine. As a short term workaround I have taken out the blocking and have put a hard sleep for the duration of the playrec with a bit extra for good measure. This is obviously not ideal and I might try to make my own stream implementation of playrec.

mgeier commented 3 years ago

I might try to make my own stream implementation of playrec

That's exactly what I would suggest as a work-around.

I'm not sure if if there is anything we can do on the sounddevice side to fix the root of the problem, though.