Skip to content

Audio Device Selection #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
djs45 opened this issue Dec 21, 2023 · 1 comment
Open

Audio Device Selection #22

djs45 opened this issue Dec 21, 2023 · 1 comment

Comments

@djs45
Copy link

djs45 commented Dec 21, 2023

Hello.

I'm working on audio 3D renderer with Python and PyOpenAL.

I have problems :

  • I can't list all audio device from my windos PC
  • So I can't select specific audio device for rendering audio
  • Will 3D audio spatialisation work with not 7.1 card (example : 10 channels pro audio board) ?

the 7.1 integrated soundboard work when selected by default on windows but I have to select another specific audio device and use PRO Audio USB Board

Many thanks for your answer and help :)

@DaFluffyPotato
Copy link

DaFluffyPotato commented Mar 23, 2025

I had a similar issue with the audio device selection not functioning correctly. It does seem that PyOpenAL's current implementation doesn't support device listing properly.

Normally you'd use alc.alcIsExtensionPresent(None, b'ALC_ENUMERATION_EXT') and alc.alcGetString(None, alc.ALC_ALL_DEVICES_SPECIFIER) to get the list of playback devices. However, ALC_ALL_DEVICES_SPECIFIER is undefined in PyOpenAL. The correct constant is 0x1013, which you can pass directly.

In theory, you should be able to do:

if alc.alcIsExtensionPresent(None, b'ALC_ENUMERATION_EXT'):
        devices = alc.alcGetString(None, 0x1013)
        print(devices)

But OpenAL's documentation indicates that the strings returned for ALC_ALL_DEVICES_SPECIFIER are null character delimited with the end being marked by a double null character. Since alc.alcGetString() was bound to the CDLL with the return type ctypes.c_char_p, I'm guessing everything after the first null character gets dropped.

Here's the hack I came up with for reading these weird string lists:

import ctypes

from openal import alc

alc.ALC_ALL_DEVICES_SPECIFIER = 0x1013

def alcGetStringList(*args):
    alc.alcGetString.restype = ctypes.POINTER(ctypes.c_char)
    str_p = alc.alcGetString(*args)
    alc.alcGetString.restype = ctypes.c_char_p
    items = []
    item = b''
    for char in str_p:
        if char == b'\0':
            if not len(item):
                break
            items.append(item.decode('utf-8', errors='replace'))
            item = b''
        else:
            item += char
    return items

def list_audio_devices():
    device_list = []
    if alc.alcIsExtensionPresent(None, b'ALC_ENUMERATION_EXT'):
        return alcGetStringList(None, alc.ALC_ALL_DEVICES_SPECIFIER)
    else:
        print('WARNING: OpenAL device enumeration extension is not available.')
    return device_list
        
print(list_audio_devices())

Which gives me:

['OpenAL Soft on Headphones (3- High Definition Audio Device)', 'OpenAL Soft on Headphones (Oculus Virtual Audio Device)', 'OpenAL Soft on Digital Audio (S/PDIF) (3- High Definition Audio Device)']

THIS SOLUTION IS NOT THREAD SAFE! It temporarily overrides the default return type for alc.alcGetString() so that it can get what it needs. I don't work with DLL's much, so I'm not sure how to copy the DLL function pointer so I can have a separate function alias for the different return type.

I may look into making a PR to address the issue at some point, but looking into what the correct implementation would be would take a bit of time since the expected use case for alcGetString() based on the OpenAL documentation depends on some pointer shenanigans, which isn't very pythonic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants