mk-fg / python-pulse-control

Python high-level interface and ctypes-based bindings for PulseAudio (libpulse)
https://pypi.org/project/pulsectl/
MIT License
170 stars 36 forks source link

The PA_SAMPLE_FLOAT32BE value is incorrect #76

Closed xdegaye closed 1 year ago

xdegaye commented 1 year ago

PA_SAMPLE_FLOAT32BE is set to 6 in the sample.h libpulse header, but it is set to 5 in _pulsectl.py.

In sample.h, PA_SAMPLE_FLOAT32NE (NE stands for native order) is defined as PA_SAMPLE_FLOAT32LE(5) or PA_SAMPLE_FLOAT32BE(6) depending on the endianness of the processor. And PA_SAMPLE_FLOAT32 is defined as PA_SAMPLE_FLOAT32NE. The Python sys module has the sys.byteorder variable to get the native byte order. Using this variable allows to get the same definition for PA_SAMPLE_FLOAT32 as done in sample.h.

FWIW the following Python script uses the preprocessor of gcc to print as Python statements the constants defined by enums in the libpulse header def.h (and also sample.h that is included by def.h):

import re
import subprocess

enums_re = r'typedef\s+enum\s+pa_.*{([^}]+)}\s+(pa_\w+)\s*;'
constant_re = r'(\w+)\s*=\s*(0x[0-9A-Fa-f]+|-?\d+)'

def libpulse_enums(pathname):
    """Parse enums in a libpulse header.

    Print the constants as Python statements.
    """

    proc = subprocess.run(['gcc', '-E', '-P', pathname],
                          capture_output=True, text=True)

    enums = re.findall(enums_re, proc.stdout, flags=re.MULTILINE)
    for enum, name in enums:
        print(f'# Enum {name}.')
        i = 0
        for constant in (x.strip() for x in enum.split(',')):
            if not constant:
                continue
            m = re.match(constant_re, constant)
            if m is not None:
                val = m.group(2)
                print(f'{m.group(1)} = {val}')
                i = eval(val)
            else:
                print(f'{constant} = {i}')
            i += 1
        print()

libpulse_enums('/usr/include/pulse/def.h')
mk-fg commented 1 year ago

Yeah, seem to be the case, probably copy-pasted the constant from pavucontrol, made off-by-one error that ended up working right on x86 (LE), and didn't look at it again. Changed to use LE/BE based on sys.byteorder in fa1080c, which I think how it should work with ctypes floats.

Requiring header files and parsing those dynamically is out of scope in this module, but yeah, maybe should use some kind of code next time, instead of (mis-)counting such enum values in a header file visually. Though using C code with a single printf() is probably easier and more robust way to do it than using custom python parser. Or modules like pycparser and cffi do such stuff in python-land as well, of course.

Anyhow, should be fixed. Thanks for reporting.