ctypes python e segfault pulseaudio após a atualização para 12.04

1

Hoje eu finalmente dei um mergulho e atualizei minha máquina desktop de 11,10 para 12,04 usando o processo de atualização, não uma nova instalação. Uma das primeiras coisas que testei após a atualização foi uma pequena biblioteca python que reproduzi o som pulseaudio, basicamente uma porta python 2.x da resposta para link

Este código agora segmenta na linha final, enquanto não fazia antes da atualização:

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))

O que é estranho é que este mesmo código funciona em um laptop que eu fiz que eu fiz uma nova instalação do 12.04 em alguns meses atrás. Estou executando o python 2.7.3 em ambos os sistemas.

Alguma idéia do que está quebrado? Funciona para os outros?

EDIT: Devo notar que eu posso fazer isso ficar fraco, brincando um pouco, por exemplo uma vez apenas adicionando algumas linhas de impressão para depuração antes da chamada pa_simple_new, consegui evitar um segfault e tudo funcionou corretamente, mas essa solução parece instável e hacky, algo está errado aqui.

EDIT2: Aqui está o backtrace (espero que eu esteja fazendo certo, eu não tenho experiência com o 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: Executando o mesmo script no meu laptop (onde não há segfault) a única diferença é que o frame 2 é em vez de ffi_call_SYSV () e depois vamos para as versões de 32 bits do pulseaudio. Eu ficaria surpreso se 32 vs 64 bits é o problema, porém, este código funcionou muito bem nesta mesma máquina antes da atualização para 12.04.

    
por Andrew Rosenfeld 28.07.2012 / 21:33

1 resposta

1

Finalmente consertado. A moral da história: tenha cuidado ao copiar o código da Internet que "parece certo", mas você não trabalhou realmente sozinho.

A string de buffer de struct spec spec acima é na verdade uma pequena modificação do código para Como escrevo bytes brutos para um dispositivo de som? - força a ordem de bytes nativa, mas não o alinhamento via"=", ao contrário do código original que possui byte nativo ordem e alinhamento (como uma implementação C teria). Não me lembro por que eu teria feito isso. No entanto, nem funciona, e não consigo entender como as coisas possivelmente funcionaram antes. Primeiro, "L" em um sistema de 64 bits é de 8 bytes, enquanto o código pulseaudio (pulse / sample.h) está usando tipos inteiros de 32 bits (4 bytes) para os 2 primeiros campos da estrutura. Segundo, usar o c_buffer sem um argumento length está adicionando um terminador nulo, na verdade aumentando o tamanho do buffer representando a estrutura por um byte.

Você pensaria que consertar isso faria tudo ficar feliz, mas não. Eu criei um buffer usando os bytes gerados vs despejar a memória de um programa C equivalente, e ainda não há dados. Algo deve estar acontecendo em ctypes. Depois de realmente ler a página ctypes do início ao fim, em vez de apenas dar uma olhada no que eu preciso, resolvi seguindo mais implementação ctypes-strict, usando uma subclasse de Estrutura real (e passando ponteiros nulos como "None", o que não muda as coisas, mas ainda é legal):

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))

Não segfault, e todo o meu código antigo para bombear o áudio através disso funciona. Ufa!

    
por Andrew Rosenfeld 02.08.2012 / 05:57