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

API - query_devices - design issue #426

Open QuadCorei8085 opened 2 years ago

QuadCorei8085 commented 2 years ago

There are a few issues with sd.query_devices

1) the api has 2 arguments, "device" and "kind". When used they are filtering for devices named similar. However on windows when a USB audio device is plugged in they will be duplicated with several host apis for example: [7] Hangszórók (19- SpeechMike III), MME [15] Hangszórók (19- SpeechMike III), Windows DirectSound [18] Hangszórók (19- SpeechMike III), Windows WASAPI issue: The query_devices will throw an exception that is not configurable (internal _query_devices has this option) and the returnvalue/list can not be used afterwards.

2) When filtering for the name "SpeechMike III), MME" the API does not return the ID of that device AND on top the name returned of the dictionary element will be just "SpeechMike III". issue: This dictionary element can not be passed to the Stream "device" parameter. When the name is used the Stream throws an exception that there are multiple devices with such (query devices returned) name (as the hostapi is not settable). And passing the ID is not an option.

At the moment query_devices can not be used with the arguments as designed. The user needs to call the function with no arguments and filter their own to figure out which device is needed.

mgeier commented 2 years ago

Thanks for this report!

The first point is intentional.

If you have a suggestion for an improvement (that's backwards compatible), please let me know!

What would you expect to happen in the case you are describing?

internal _query_devices has this option

Can you please clarify which function and which option you mean?

The second point is not intentional.

Using 'SpeechMike III), MME' is supposed to give you the right device. Maybe there is a problem/bug when there is punctuation?

Did you try 'SpeechMike MME'? 'speech mme' should also work.

QuadCorei8085 commented 2 years ago

Sorry I typed fast and made a mistake there, the internal function is actually called _get_device_id it has a parameter raise_on_error that raises an exception OR just returns -1.

Problem with 1) is that you cant get a list of a devices that you are interested in -> you get the exception and a printout.

Overall what I would expect is such scenario:

When query_devices is called with empty arguments I get this list:

   0 Microsoft Sound Mapper - Input, MME (2 in, 0 out)
>  1 Headset Microphone (Plantronics, MME (2 in, 0 out)
   2 Microphone Array (Intel® Smart , MME (2 in, 0 out)
   3 Microphone (18- SpeechMike III), MME (2 in, 0 out)
   4 Microsoft Sound Mapper - Output, MME (0 in, 2 out)
<  5 Headset Earphone (Plantronics B, MME (0 in, 2 out)
   6 Hangszórók (18- SpeechMike III), MME (0 in, 2 out)
   7 Lautsprecher (Realtek(R) Audio), MME (0 in, 2 out)
   8 Primary Sound Capture Driver, Windows DirectSound (2 in, 0 out)
   9 Headset Microphone (Plantronics Blackwire 325.1), Windows DirectSound (2 in, 0 out)
  10 Microphone Array (Intel® Smart Sound Technology (Intel® SST)), Windows DirectSound (2 in, 0 out)
  11 Microphone (18- SpeechMike III), Windows DirectSound (2 in, 0 out)
  12 Primary Sound Driver, Windows DirectSound (0 in, 2 out)
  13 Headset Earphone (Plantronics Blackwire 325.1), Windows DirectSound (0 in, 2 out)
  14 Hangszórók (18- SpeechMike III), Windows DirectSound (0 in, 1 out)
  15 Lautsprecher (Realtek(R) Audio), Windows DirectSound (0 in, 2 out)
  16 Hangszórók (18- SpeechMike III), Windows WASAPI (0 in, 1 out)
  17 Lautsprecher (Realtek(R) Audio), Windows WASAPI (0 in, 2 out)
  18 Headset Earphone (Plantronics Blackwire 325.1), Windows WASAPI (0 in, 2 out)
  19 Headset Microphone (Plantronics Blackwire 325.1), Windows WASAPI (2 in, 0 out)
  20 Microphone Array (Intel® Smart Sound Technology (Intel® SST)), Windows WASAPI (4 in, 0 out)
  21 Microphone (18- SpeechMike III), Windows WASAPI (1 in, 0 out)
  22 Microphone (Realtek HD Audio Mic input), Windows WDM-KS (2 in, 0 out)
  23 Stereo Mix (Realtek HD Audio Stereo input), Windows WDM-KS (2 in, 0 out)
  24 Headphones 1 (Realtek HD Audio 2nd output with SST), Windows WDM-KS (0 in, 2 out)
  25 Headphones 2 (Realtek HD Audio 2nd output with SST), Windows WDM-KS (0 in, 2 out)
  26 PC Speaker (Realtek HD Audio 2nd output with SST), Windows WDM-KS (2 in, 0 out)
  27 Line In (Realtek HD Audio Line input), Windows WDM-KS (2 in, 0 out)
  28 Speakers 1 (Realtek HD Audio output with SST), Windows WDM-KS (0 in, 2 out)
  29 Speakers 2 (Realtek HD Audio output with SST), Windows WDM-KS (0 in, 2 out)
  30 PC Speaker (Realtek HD Audio output with SST), Windows WDM-KS (2 in, 0 out)
  31 Microphone (SpeechMike III), Windows WDM-KS (1 in, 0 out)
  32 Speakers (SpeechMike III), Windows WDM-KS (0 in, 1 out)
  33 Microphone Array (Intel® Smart Sound Technology (Intel® SST) Microphone), Windows WDM-KS (4 in, 0 out)
  34 Headset Microphone (Plantronics Blackwire 325.1), Windows WDM-KS (2 in, 0 out)
  35 Headset Earphone (Plantronics Blackwire 325.1), Windows WDM-KS (0 in, 2 out)

When query_devices(device='speechmike') is called I would expect:

   3 Microphone (18- SpeechMike III), MME (2 in, 0 out)
   6 Hangszórók (18- SpeechMike III), MME (0 in, 2 out)
  11 Microphone (18- SpeechMike III), Windows DirectSound (2 in, 0 out)
  14 Hangszórók (18- SpeechMike III), Windows DirectSound (0 in, 1 out)
  16 Hangszórók (18- SpeechMike III), Windows WASAPI (0 in, 1 out)
  21 Microphone (18- SpeechMike III), Windows WASAPI (1 in, 0 out)
  31 Microphone (SpeechMike III), Windows WDM-KS (1 in, 0 out)
  32 Speakers (SpeechMike III), Windows WDM-KS (0 in, 1 out)

When query_devices(device='speechmike', kind='input') I would expect:

   3 Microphone (18- SpeechMike III), MME (2 in, 0 out)
  11 Microphone (18- SpeechMike III), Windows DirectSound (2 in, 0 out)
  21 Microphone (18- SpeechMike III), Windows WASAPI (1 in, 0 out)
  31 Microphone (SpeechMike III), Windows WDM-KS (1 in, 0 out)

I would change the function to include this option for the exception too query_devices(device='speechmike', kind='input', raise_on_error=0xFFFF) or similar (were a bit represents a kind of error and FFFF means all errors) when called with raise_on_error=0 the caller can tell the function to never raise an error.

The index of the device returned should always be the absolute index that can be used for the Stream(device=device_index). Unfortunately the dictionary elements that is returned by the query_devices have a 'name' field (without the hostapi name, the hostapi is just an index which is again somewhat missleading imho, and unfortunately its not translatable which is which).

The dictionary looks like this at the moment: {'name': 'Microphone (18- SpeechMike III)', 'hostapi': 0, 'max_input_channels': 2, 'max_output_channels': 0, 'default_low_input_latency': 0.09, 'default_low_output_latency': 0.09, 'default_high_input_latency': 0.18, 'default_high_output_latency': 0.18, 'default_samplerate': 44100.0}

It would be nice to introduce a "device_index" field or similar that has the absolute index of that specific device thus when the query_devices is called with filtering and returns the above (say 3rd example 4 devices) I could immediately know what is the absolute index to be used with the Strea(device=...) (assuming the query_device will not cause an exception due to many findings).

mgeier commented 2 years ago

The user needs to call the function with no arguments and filter their own to figure out which device is needed.

When used in a non-trivial script, this is typically the best choice.

Using strings as device is just a convenience feature, mostly for interactive use, but it has its limitations.

Sorry I typed fast and made a mistake there, the internal function is actually called _get_device_id it has a parameter raise_on_error that raises an exception OR just returns -1.

OK, thanks for the clarification!

Problem with 1) is that you cant get a list of a devices that you are interested in -> you get the exception and a printout.

Yes, and I think you should not get a list. You should either get one device or an error.

Originally, device only accepted numbers (e.g. device=4) and it would not make sense to return a list of devices.

As I understand it, the parameter doesn't "filter" the list of devices, but it rather "selects" a single device.

BTW, the kind parameter doesn't "filter" the list of devices either.

In summary, query_devices() either returns a list of all devices or a single device.

The index of each of the list elements corresponds to the device index. Returning sub-lists would invalidate that.

It would be nice to introduce a "device_index" field or similar that has the absolute index of that specific device

This has been fixed in the meantime, see #404, which is available in the latest release.

Unfortunately the dictionary elements that is returned by the query_devices have a 'name' field (without the hostapi name, the hostapi is just an index which is again somewhat missleading imho, and unfortunately its not translatable which is which).

Why is the host API index misleading? Device index and host API index are directly taken from the underlying PortAudio library, I do want to expose those indices to the user. You can get the name of the host API with query_hostapis(): https://python-sounddevice.readthedocs.io/en/0.4.5/api/checking-hardware.html#sounddevice.query_hostapis


What about point 2, which might be a bug? Any news on that?