Sunday, August 15, 2010

RPython callbacks from C

It is possible to define an RPython function that is passed to a C function as a callback argument. The RPython function will then be invoked from C whenever an event causes it to trigger. Pyaudio never implemented the PortAudio async API, because it requires callback functionality and that may take a great amount of effort from the Python C API. From RPython is rather easy once we know the steps:
1. import llhelper from pypy.rpython.annlowlevel
2. define the function signature (argument types, return type) using lltype.FuncType
3. create a pointer to the signature lltype.Ptr( mysignature )
4. pass the pointer as an argument to rffi.llexternal when defining the wrapper
5. within the entrypoint use llhelper:
mycallback = llhelper(lltype.Ptr( mysignature ), my_rpython_function )
6. pass mycallback as an argument to the external C function

This excerpt is from RpyPortAudio 0.3:


mult16bits = 2**15
def stream_callback( ibuf, obuf, frameCount, info, flags, user ):
t = time.time()
for i in range(512):
raw = ibuf[i]
x=rffi.cast(lltype.Signed, raw)
samp = math.sin( t + (float(i)/512.0) ) * mult16bits
samp = int(samp) + x
obuf[i] = rffi.cast(rffi.INT, samp)
return rffi.cast(rffi.INT, 0) # 0=continue, 1=complete, 2=abort

StreamCallbackTimeInfoPtr.TO.become( StreamCallbackTimeInfo )
stream_cb_signature = lltype.FuncType([RBufferPtr, RBufferPtr, rffi.INT, StreamCallbackTimeInfoPtr, rffi.INT, rffi.VOIDP], rffi.INT)
stream_callback_ptr = lltype.Ptr( stream_cb_signature )

OpenDefaultStream = rffi.llexternal( 'Pa_OpenDefaultStream',
[
StreamRefPtr, # PaStream**
rffi.INT, # numInputChannels
rffi.INT, # numOutputChannels
rffi.INT, # sampleFormat
rffi.DOUBLE, # double sampleRate
rffi.INT, # unsigned long framesPerBuffer
stream_callback_ptr, #PaStreamCallback *streamCallback
rffi.VOIDP, #void *userData
],
rffi.INT, # return
compilation_info=eci,
)

def entrypoint():
streamptr = lltype.malloc(StreamRefPtr.TO, 1, flavor='raw') # must have length 1
userdata = lltype.nullptr(rffi.VOIDP.TO)
callback = llhelper(lltype.Ptr( stream_cb_signature ), stream_callback)
ok = OpenDefaultStream( streamptr, 2, 2, Int16, 22050.0, 512, callback, userdata )
stream = streamptr[0]
startok = StartStream( stream )
time.sleep(10.0)

No comments:

Post a Comment