Passing File-like Objects To Ctypes Callbacks
Solution 1:
A NoneType is passed to the media_read_cb as indicated by the traceback. The problem in the code seems to be the media_open_cb function. If you replace this callback with None in the media_new_callbacks function, it will not be called and media_read_cb will be called with the appropiate opaque pointer.
The reason for this is a bit obscure to me. If the open_cb is set to None, vlc will call its default open_cb, which will then set size_pointer to maxsize and data_pointer to opaque by default (which is identical to your function). Apparently, something in your code goes wrong when setting the value of the pointer. I do not know how to fix that as I am new to ctypes too.
When I run your code with:
media = instance.media_new_callbacks(None, callbacks['read'], callbacks['seek'], callbacks['close'], ctypes.byref(ctypes.py_object(stream)))
The media_read_cb is succesfully called. However, python then crashes at:
stream = ctypes.cast(opaque, ctypes.py_object).value
I do not know how to solve this either, but there is a workaround. You could set the stream variable as a global variable, so you keep the pointer yourself instead of relying on the ctypes stuff.
Writing to the buffer also does not seem to work, as the buffer is passed as a string to the media_read_cb. Since strings are immutable in python, this fails. A workaround for this is to change the CFUNCTYPE to contain a ctypes.POINTER to c_char instead of plain c_char_p (string in python). You can then populate the memory area with the bytes from the stream through iteration.
Applying these changes, your code looks like this:
import ctypes
import io
import sys
import time
import vlc
MediaOpenCb = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_uint64))
MediaReadCb = ctypes.CFUNCTYPE(ctypes.c_ssize_t, ctypes.c_void_p, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t)
MediaSeekCb = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_uint64)
MediaCloseCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)
stream=Nonedefmedia_open_cb(opaque, data_pointer, size_pointer):
data_pointer.value = opaque
size_pointer.contents.value = sys.maxsize
return0defmedia_read_cb(opaque, buffer, length):
new_data = stream.read(length)
for i inrange(len(new_data)):
buffer[i]=new_data[i]
returnlen(new_data)
defmedia_seek_cb(opaque, offset):
stream.seek(offset)
return0defmedia_close_cb(opaque):
stream.close()
callbacks = {
'open': MediaOpenCb(media_open_cb),
'read': MediaReadCb(media_read_cb),
'seek': MediaSeekCb(media_seek_cb),
'close': MediaCloseCb(media_close_cb)
}
defmain(path):
global stream
stream = open(path, 'rb')
instance = vlc.Instance('-vvv')
player = instance.media_player_new()
media = instance.media_new_callbacks(None, callbacks['read'], callbacks['seek'], callbacks['close'], ctypes.byref(ctypes.py_object(stream)))
player.set_media(media)
player.play()
whileTrue:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
And it succesfully runs!
Of course, instead of using a global variable, it would be better to wrap all this inside a python class.
EDIT: I figured out how to set the data_pointer appropiately. Here is the code:
import ctypes
import io
import sys
import time
import vlc
MediaOpenCb = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_uint64))
MediaReadCb = ctypes.CFUNCTYPE(ctypes.c_ssize_t, ctypes.c_void_p, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t)
MediaSeekCb = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_uint64)
MediaCloseCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)
defmedia_open_cb(opaque, data_pointer, size_pointer):
data_pointer.contents.value = opaque
size_pointer.contents.value = sys.maxsize
return0defmedia_read_cb(opaque, buffer, length):
stream=ctypes.cast(opaque,ctypes.POINTER(ctypes.py_object)).contents.value
new_data = stream.read(length)
for i inrange(len(new_data)):
buffer[i]=new_data[i]
returnlen(new_data)
defmedia_seek_cb(opaque, offset):
stream=ctypes.cast(opaque,ctypes.POINTER(ctypes.py_object)).contents.value
stream.seek(offset)
return0defmedia_close_cb(opaque):
stream=ctypes.cast(opaque,ctypes.POINTER(ctypes.py_object)).contents.value
stream.close()
callbacks = {
'open': MediaOpenCb(media_open_cb),
'read': MediaReadCb(media_read_cb),
'seek': MediaSeekCb(media_seek_cb),
'close': MediaCloseCb(media_close_cb)
}
defmain(path):
stream = open(path, 'rb')
instance = vlc.Instance()
player = instance.media_player_new()
media = instance.media_new_callbacks(callbacks['open'], callbacks['read'], callbacks['seek'], callbacks['close'], ctypes.cast(ctypes.pointer(ctypes.py_object(stream)), ctypes.c_void_p))
player.set_media(media)
player.play()
whileTrue:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
Solution 2:
I fixed the problem, this is the perfect way:
import ctypes
import sys
import time
import os
import vlc
import logging
import ctypes
import threading
classVirFile(object):
def__init__(self, fpath):
self.fpath = fpath
_obj_cache = {}
_obj_lock = threading.Lock()
defcache_object(key: int, *arg):
with _obj_lock:
if key in _obj_cache:
raise RuntimeError(f'cache_object: key duplicate: {key}')
_obj_cache[key] = arg
defcache_remove(key: int):
with _obj_lock:
if key notin _obj_cache:
raise RuntimeError(f'cache_remove: key not found: {key}')
del _obj_cache[key]
@vlc.cb.MediaOpenCbdefmedia_open_cb(opaque, data_pointer, size_pointer):
try:
vf = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
ifnot os.path.isfile(vf.fpath):
raise RuntimeError(f'file not exists: {vf.fpath}')
outf = open(vf.fpath, 'rb')
p1 = ctypes.py_object(outf)
p2 = ctypes.pointer(p1)
p3 = ctypes.cast(p2, ctypes.c_void_p)
cache_object(id(outf), outf, p1, p2, p3)
data_pointer.contents.value = p3.value
size_pointer.contents.value = os.stat(vf.fpath).st_size
except Exception as e:
logging.exception('media_open_cb')
return -1return0@vlc.cb.MediaReadCbdefmedia_read_cb(opaque, buffer, length):
try:
outf = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
data = outf.read(length)
sz = len(data)
ctypes.memmove(buffer, data, sz)
return sz
except Exception as e:
logging.exception('media_read_cb')
return -1@vlc.cb.MediaSeekCbdefmedia_seek_cb(opaque, offset):
try:
outf = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
outf.seek(offset)
except Exception as e:
logging.exception('media_seek_cb')
return -1return0@vlc.cb.MediaCloseCbdefmedia_close_cb(opaque):
try:
outf = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
outf.close()
cache_remove(id(outf))
except Exception as e:
logging.exception('media_close_cb')
returndefget_vf(path):
vf = VirFile(path)
p1 = ctypes.py_object(vf)
p2 = ctypes.pointer(p1)
p3 = ctypes.cast(p2, ctypes.c_void_p)
key = id(vf)
cache_object(key, p1, p2, p3)
return key, p3
defmain(path):
key, p = get_vf(path)
instance = vlc.Instance()
player = instance.media_player_new()
media = instance.media_new_callbacks(
media_open_cb,
media_read_cb,
media_seek_cb,
media_close_cb,
p)
player.set_media(media)
player.play()
time.sleep(10)
player.stop()
time.sleep(3)
media.release()
player.release()
cache_remove(key)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
Post a Comment for "Passing File-like Objects To Ctypes Callbacks"