spatialaudio / python-sounddevice

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

How can I update the Query_devices() list during un stream to check if the recording device is still there ? #382

Closed Wawi36 closed 2 years ago

Wawi36 commented 2 years ago

Hello,

I have a question about updating sd.query_devices () during a Stream to implement a security in case the recording device plug-off accidentally.

I noticed and read in the old issue that Sounddevice froze the list of devices when initialized (du to PortAudio) [#125]

In issue #47 there is proposed to Terminate and Initialize Portaudio (I tried and it works) but the drawback is that all streams are closed (and it's a problem for continuous acquisition ). So @melvyn2 and @refeved (in issue #125) proposed a solution, using 'hotplug fork'. I didn't know what it was, so I investigated. At the end I couldn't figure how to use 'hotplug fork'.

Do someone know a way to update the query_devices without Terminate the PortAudio and without using 'hotplug fork'? Or Can someone explain me witch of the file in the links given by @melvyn2 and @refeved I should update in the PortAudio library/folder? And how to use it after?

Thank you and have a nice day

PS : Between I'm a Windows user ^^

mgeier commented 2 years ago

Do someone know a way to update the query_devices without Terminate the PortAudio and without using 'hotplug fork'?

I don't think there is a way.

This is an issue where the progress of the "hotplug" feature is tracked: https://github.com/PortAudio/portaudio/issues/11

This is a branch with the "latest" developments (quite a few years old): https://github.com/PortAudio/portaudio/tree/hotplug

Here's a Wiki page with a lot of information: https://github.com/PortAudio/portaudio/wiki/HotPlug

This seems to be a fork (also quite old) with "hotplug" enabled, but I haven't tried it: https://github.com/jitsi/portaudio

refeved commented 2 years ago

@Wawi36 I'm able to compile and install https://github.com/jitsi/portaudio on my Raspberry Pi(Running Linux Ubuntu Mate). After install that version of portaudio, then I get an new API called Pa_UpdateAvailableDeviceList() which update device list without interrupt on going streaming.

To call that API in sounddevice package, I created an function in sounddevice.py like below:

def refresh_portaudio_device_list():
    _check(_lib.Pa_UpdateAvailableDeviceList())

However, I'm not sure if this will work on Windows.

Wawi36 commented 2 years ago

Hello @mgeier and @refeved,

Thanks for the quick answers.

@mgeier, I took a look to all the linkes you gives and it makes me realize that they are beyond my actuals limited knowledge of computing. I couldn’t understand what I should do with the RossBencina Hotplug files, how to install it. Neither what was a the libjitsi fork and how to intall it too.

I done some research and end up with being completely mist up with ‘ways’ to install it. I’m not very accustumed with GitHub (I just begen using it) so I don’t really know what to do with the libjitsi folder. I tried using pip install like I found in Stackoverflow forum but it didn’t works. 'm missing something to understand. May one of you (@mgeier or @refeved) can explain me how to do it?

Best regards.

mgeier commented 2 years ago

PortAudio is not a Python library, that's why pip doesn't work.

There are multiple ways to compile the PortAudio library, and it can be complicated.

I could probably help with the compilation on Linux, but I have no idea how to do it on Windows, sorry.

Wawi36 commented 2 years ago

Thanks @mgeier, I will dig more on the compiling library methods to python on Windows. I will close the issue because it's no more one. I just need to strengthening my knowledge on computing.

KiDo-Ruan commented 1 year ago

@refeved I am facing same problem with portaudio on Mac, can you tell to me how do fix portaudio on Mac OS? I install Portaudio by brew and I can't update audio device when I change device :(

rec commented 7 months ago

I too have come to this point and I want to update any interested readers with my results. I could easily move this elsewhere if the maintainers felt it was more appropriate!


I'm working on a Universal Recorder https://github.com/rec/recs and of course it needs to be robust against devices coming on and offline.

It looks as if https://github.com/PortAudio/portaudio/issues/11 is indefinitely stalled, and any developer should assume that it won't get fixed and that code then get into sounddevice/sd for years, if ever.

(Open source developers are generally uncompensated and underthanked, it's a marvel we get open source at all. Thanks to the sounddevice people for this excellent Python library.)


Given that fixed point, there is really only one solution - run PortAudio and sd in subprocesses.

Python offers two ways to do this: subprocess and multiprocessing (mp). Both are doable, neither is trivial.

Because there is likely to have to be two-way communication between the processes in an audio application, mp probably offers a better fit, though it is harder to use.

I just wrote a tiny test:

while True:
    subprocess.run('python -m sounddevice'.split())
    time.sleep(2)

and plugged and unplugged a device, and indeed, it appeared and disappeared from the output!


I believe that mp will be a bit trickier, because I think I will need to spawn off the subprocesses before opening sd/PortAudio library, which means, if my logic is correct, that I never use sd in the main process!, but the above proves it is at least possible.

My plan is to first try this and see, by loading sd in the main process, calling query_devices() and then spawning off a mp.Process every couple of seconds to call the same thing.

I'll learn one way or the other quite fast.

Answer: it is only a problem if you use the much-faster fork after you have loaded the sounddevice library (and PortAudio with it, that's the real cause):

import multiprocessing as mp

def _query():
    from sounddesign import query_devices

    info = query_devices(kind=None)
    print(sorted(i['name'] for i in info))

def main():
    if USE_FORK:
        mp.set_start_method('fork')

    if IMPORT_FIRST:
        _query()

    while True:
        mp.Process(target=_query).start()
        time.sleep(2)

This code sees devices being unplugged, unless both USE_FORK and IMPORT_FIRST are true.


Whether you use mp or subprocess, you will need to have one main process, one process for each device, and likely create short-lived processes just to call sd.query_devices().

I hope to have fairly soon some sort of working code for others to use as a model.


Thanks so much again to the maintainers of sounddevice - I couldn't do it without you! ❤️