![升级到 12.04 后,python ctypes 和 pulseaudio 出现段错误](https://linux22.com/image/1096544/%E5%8D%87%E7%BA%A7%E5%88%B0%2012.04%20%E5%90%8E%EF%BC%8Cpython%20ctypes%20%E5%92%8C%20pulseaudio%20%E5%87%BA%E7%8E%B0%E6%AE%B5%E9%94%99%E8%AF%AF.png)
今天我终于下定决心,使用升级过程将我的台式机从 11.10 升级到 12.04,而不是全新安装。升级完成后,我测试的第一件事就是我的一个小型 Python 库,它通过 pulseaudio 播放声音,基本上是 Python 2.x 版的答案https://askubuntu.com/a/33602/55992
这段代码现在在最后一行出现段错误,而升级之前并没有出现这种情况:
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))
奇怪的是,同样的代码可以在几个月前我全新安装 12.04 的笔记本电脑上运行。我在两个系统上运行的都是 Python 2.7.3。
知道哪里出了问题吗?它对其他人有用吗?
编辑:我应该注意到,我可以通过稍微摆弄它来让它顺利运行,例如,只需在 pa_simple_new 调用之前添加一些打印行进行调试,我就能避免段错误,并且一切都正常工作,但这个解决方案看起来很粗糙和不稳定,这里有些问题。
EDIT2:这是回溯(希望我做得对,我没有使用 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:在我的笔记本电脑上运行相同的脚本(没有段错误),唯一的区别是第 2 帧改为 ffi_call_SYSV(),然后我们进入 32 位版本的 pulseaudio。不过,如果 32 位与 64 位是问题所在,我会感到惊讶,在升级到 12.04 之前,此代码在同一台机器上运行良好。
答案1
终于修好了。这个故事的寓意是:小心不要从互联网上复制“看起来正确”但你还没有真正自己解决过的代码。
上面的 spec struct buffer 字符串示例实际上是对代码进行了轻微的修改,以 如何将原始字节写入声音设备? - 它强制使用本机字节顺序,但不通过“=”进行对齐,这与具有本机字节顺序和对齐的原始代码不同(就像 C 实现那样)。我不记得我为什么要这样做。但是,两者都不起作用,我不明白以前是怎么回事。首先,64 位系统上的“L”是 8 个字节,而 pulseaudio 代码 (pulse/sample.h) 对结构的前 2 个字段使用 32 位(4 字节)整数类型。其次,使用没有长度参数的 c_buffer 会添加一个空终止符,实际上将表示结构的缓冲区的长度增加了一个字节。
你可能会认为修复这个问题会让一切都好起来,但事实并非如此。我使用生成的字节创建了一个缓冲区,而不是从等效的 C 程序中转储内存,但仍然没有成功。ctypes 中一定发生了什么。在实际阅读了ctypes 页面 从头到尾,我并没有只是浏览一下我需要的内容,而是决定采用以下更严格的 ctypes 实现,使用实际的结构子类(并将空指针传递为“None”,这不会改变任何事情但仍然很好):
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))
没有段错误,我之前的所有音频传输代码都正常工作。呼!