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!