1

Today I finally took the plunge and upgraded my desktop machine from 11.10 to 12.04 using the upgrade process, not a fresh install. One of the first things I tested after the upgrade completed was a little python library I have that plays sound through pulseaudio, basically a python 2.x port of the answer to https://askubuntu.com/a/33602/55992

This code now segfaults at the final line, whereas it didn't before the upgrade:

import ctypes
import struct

PA_SAMPLE_FLOAT32LE = 5
PA_STREAM_PLAYBACK = 1

pa = ctypes.cdll.LoadLibrary("libpulse-simple.so.0")
pa.pa_strerror.restype = ctypes.c_char_p

pat_sample_spec = ctypes.c_buffer(struct.pack("=LLB", PA_SAMPLE_FLOAT32LE, 44100, 2))    
error = ctypes.c_int(0)

s = pa.pa_simple_new(0, "Python", PA_STREAM_PLAYBACK, 0, "Test", ctypes.byref(pat_sample_spec), 0, 0, ctypes.byref(error))

What is weird is that this same code works on a laptop I have that I did a fresh install of 12.04 on a couple months ago. I am running python 2.7.3 on both systems.

Any ideas on what is broken? Does it work for others?

EDIT: I should note that I can get this to limp along by messing around with it a bit, e.g. once just by adding a few print lines for debugging before the pa_simple_new call I was able to avoid a segfault and everything worked properly, but this solution seems hacky and unstable, something is wrong here.

EDIT2: Here is the backtrace (hope I am doing this right, I am not experienced with gdb):

#0  0x00007ffff5d7ba69 in pa_channel_map_valid () from /usr/lib/x86_64-linux-gnu/libpulse.so.0
#1  0x00007ffff5fb99a7 in pa_simple_new () from /usr/lib/x86_64-linux-gnu/libpulse-simple.so.0
#2  0x00007ffff61d6ea4 in ffi_call_unix64 () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#3  0x00007ffff61d68c5 in ffi_call () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#4  0x00007ffff61c72c2 in _ctypes_callproc () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#5  0x00007ffff61c7aa2 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#6  0x00000000004c7c76 in PyObject_Call ()
#7  0x000000000042aa4a in PyEval_EvalFrameEx ()
#8  0x00000000004317f2 in PyEval_EvalCodeEx ()
#9  0x000000000054b171 in PyRun_FileExFlags ()
#10 0x000000000054b7d8 in PyRun_SimpleFileExFlags ()
#11 0x000000000054c5d6 in Py_Main ()
#12 0x00007ffff68e576d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#13 0x000000000041b931 in _start ()

EDIT3: Running the same script on my laptop (where there is no segfault) the only difference is that frame 2 is instead ffi_call_SYSV() and then we go into 32-bit versions of pulseaudio. I would be surprised if 32 vs 64 bit is the issue though, this code worked just fine on this same machine before the upgrade to 12.04.

2
  • did you try to do a backtrace? where is the segfault? Jul 28, 2012 at 20:27
  • Hmm, for what it's worth the python3 code in askubuntu.com/a/33602/55992 now (post-upgrade) doesn't work for me either. I may abandon using ctypes for this and try cython or similar. Jul 31, 2012 at 0:53

1 Answer 1

1

Finally fixed it. The moral of the story: be careful about copying code off the internet that "looks right" but you haven't really worked through yourself.

The sample spec struct buffer string above is actually a slight modification of the code to How do I write raw bytes to a sound-device? - it forces native byte order but no alignment via the "=", unlike the original code which has native byte order and alignment (like a C implementation would have). I can't remember why I would have ever done that. However, neither works, and I can't understand how things possibly worked before. First, "L" on a 64-bit system is 8 bytes, whereas the pulseaudio code (pulse/sample.h) is using 32-bit (4 byte) integer types for the first 2 fields of the struct. Second, using c_buffer without a length argument is adding a null terminator, actually increasing the length of the buffer representing the struct by one byte.

You would think fixing this would make everything happy, but no. I created a buffer using the bytes generated vs dumping the memory from an equivalent C program, and still no dice. Something must be going on in ctypes. After actually reading the ctypes page from start to finish rather than just skimming it for what I need, I settled upon the following more ctypes-strict implementation, using an actual Structure subclass (and passing null pointers as "None", which doesn't change things but is still nice):

import ctypes
import struct

PA_SAMPLE_FLOAT32LE = 5
PA_STREAM_PLAYBACK = 1

pa = ctypes.cdll.LoadLibrary("libpulse-simple.so.0")

class SampleSpec(ctypes.Structure):
    _fields_ = [("format", ctypes.c_int), ("rate", ctypes.c_int), ("channels", ctypes.c_byte)]

pa_sample_spec = SampleSpec(PA_SAMPLE_FLOAT32LE, 44100, 2)

error = ctypes.c_int(0)

s = pa.pa_simple_new(None, "Python", PA_STREAM_PLAYBACK, None, "Test", ctypes.byref(pa_sample_spec), None, None, ctypes.byref(error))

No segfault, and all my old code to pump audio through this works. Phew!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .