bastibe / SoundCard

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

Runtime error when attempting to initialize player in "exclusive_mode" #142

Open jmw182 opened 2 years ago

jmw182 commented 2 years ago

Hi,

I am attempting to generate real-time audio with low latency on a Windows 10 system with python 3.7. If I try to set the exclusive_mode argument to speaker.player() to True, mediafoundation.py throws an error at line 92: RuntimeError: invalid argument

Here is a snippet of code that generates the error for me:

speaker = soundcard.default_speaker()
with speaker.player(samplerate=48000, blocksize=1200, exclusive_mode=True) as audioplayer:
    pass

The values of samplerate and blocksize (and channels, which I left as default) do not seem to matter here, the error always happens when I set exclusive_mode=True.

Here is the full error when I run the above snippet in an interactive session:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python\Anaconda3\lib\site-packages\soundcard\mediafoundation.py", line 435, in player
    return _Player(self._audio_client(), samplerate, channels, blocksize, False, exclusive_mode)
  File "C:\Python\Anaconda3\lib\site-packages\soundcard\mediafoundation.py", line 542, in __init__
    _com.check_error(hr)
  File "C:\Python\Anaconda3\lib\site-packages\soundcard\mediafoundation.py", line 92, in check_error
    raise RuntimeError("invalid argument")
RuntimeError: invalid argument

My PC has Realtek audio:

>>> print(speaker)
<Speaker Speakers (Realtek(R) Audio) (2 channels)>

Thanks! Overall this library seems very helpful and a bit easier to use than pyaudio.

bastibe commented 2 years ago

I suspect that the error is Windows' way of telling you that you can't open this device in exclusive mode. Perhaps because some other process is using it, or because exclusive mode is not available for every kind of sound card?

fmorillo commented 2 years ago

Hm I have the same error with different devices. With the python sounddevice library and other applications I can activate the exclusive mode.

bastibe commented 2 years ago

In that case this might be a bug. I'd be grateful for any help in debugging this, as I don't currently have a lot of time to devote to open source at the moment.

jmw182 commented 2 years ago

Thanks for the response @bastibe and thanks for commenting about your error @fmorillo. I had some time to take a look at this tonight and made some progress. In _AudioClient.__init__, the streamflag 0x00100000 is incompatible with exclusive_mode (See MS docs here).

Moving the streamflag definition into the if exclusive_mode/else block to only use that flag for shared mode may fix this issue. Now my code snippet from the OP still returns an error, but instead of "invalid argument" it returns "unsupported format" which seems like another rabbit hole for another day. Still, this seems like progress.

Some relevant documentation for the "unsupported format" error: https://docs.microsoft.com/en-us/windows/win32/coreaudio/device-formats https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-isformatsupported https://docs.microsoft.com/en-us/windows/win32/coreaudio/exclusive-mode-streams https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible?redirectedfrom=MSDN https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize

bastibe commented 2 years ago

Brilliant! Thank you for investigating this!

So we might be restricted to a pre-set sample rate if we open a device in exclusive mode. Or perhaps we have to change the device's sample rate, as opposed to the AudioClient's.

jmw182 commented 2 years ago

I pushed a relevant commit (5060713) to my fork, but I'm not sure if there's any point in making a pull request since I do not actually have exclusive mode working.

bastibe commented 2 years ago

You can create the pull request at any time if you want to ask for help, just mark is as work-in-progress. I'm afraid I can't help much at the moment, sorry.