bastibe / SoundCard

A Pure-Python Real-Time Audio Library
https://soundcard.readthedocs.io
BSD 3-Clause "New" or "Revised" License
680 stars 69 forks source link

No error on bluetooth device disconnect using Linux #123

Open shawnc722 opened 3 years ago

shawnc722 commented 3 years ago

With audio playing out of the default speaker and a non-default bluetooth speaker connected, I recorded loopback from the default speaker and played it out of the bluetooth one. If I turn off the bluetooth speaker, no errors are thrown like they would be on Windows. Instead, soundcard starts playing the audio out of the default speaker, causing constantly increasing feedback. Using the opposite scenario, recording loopback from a bluetooth speaker and playing out of another one, works as you'd expect until the bluetooth speaker is disconnected. Then the _Microphone object switches to the default microphone, making the speaker play whatever the mic hears. This seems like unintended behaviour if not a bug. In windows, two errors will be thrown in the same situation: Error 0x88890004 and Error 0x100000001. Thanks for all your work on this project!

bastibe commented 3 years ago

I think pulseaudio has a way of registering a callback for when devices change. But soundcard is currently not set up to handle such events, as you have noticed.

I'd be grateful for a pull request, if you'd like to look into this in more detail.

shawnc722 commented 3 years ago

Sorry in advance, I think this is probably a stupid question with a simple answer. I've made a branch with some changes that enable the callback you mentioned, and it can get the type of device (source/sink) and the type of event (add/remove/change), as well as the index of the device. I also added the ability to get a device by its index, and those parts both work with no issues. My problem is when I try to get a device from that callback function; any interaction with _pulse just crashes the program without any errors. I'm fairly certain it's something to do with threading, but I'm lost as to exactly what or how to get around that and I have a feeling it's a simple fix you've probably dealt with while creating the rest of the library. The relevant code starts at line 76 on https://github.com/shawnc722/SoundCard/blob/master/sc/pulseaudio.py. To get that crash, I try to access _pulse.sink_list from within the _context_subscription_callback() function and run testing.py to test. Also, I promise I'll clean up the code (and learn how to use git without changing a bunch of file names) before submitting any pull requests lol.

bastibe commented 3 years ago

You probably have to lock the mainloop first.

Instead of calling your_function(...) directly, try calling _lock_and_block(your_function)(...).

shawnc722 commented 3 years ago

That was one of the first things I tried, but I got the same crash without an error. I narrowed down the cause of the error to the with self._lock_mainloop: part, so it seems to be the same issue of accessing any of _pulse’s functions that crashes it. In the meantime I’ve built a workaround that allows calling arbitrary functions by using a queue and an extra thread, but it seems to defeat the purpose of using a callback when the response is no longer immediate. Another solution I considered is to just throw a RuntimeError as soon as the current device changes, to match the windows behaviour, but I’ve found that raising an exception from within the callback can’t be captured by a client’s except because they’re on different threads.

I’ll try to figure out some more options once I have time to work on this again. Thanks for your suggestion!