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 = 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.

  • did you try to do a backtrace? where is the segfault? – Janus Troelsen Jul 28 '12 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. – Andrew Rosenfeld Jul 31 '12 at 0:53

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 = 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!

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.