将类文件对象传递给 ctypes 回调
Passing file-like objects to ctypes callbacks
我正在尝试使用 LibVLC Python 绑定来播放内存中的流(Python 3.4,Windows 7,LibVLC 3.x)。最终,我的目标是将数据馈送到 BytesIO 实例,然后 VLC 将从该实例读取和播放。但目前,我决定破解一个快速脚本来尝试从文件流中读取。这是代码和回溯 - 说我是 ctypes 的新手是一种轻描淡写的说法,所以有人知道我做错了什么吗?
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.c_char_p, 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)
def media_open_cb(opaque, data_pointer, size_pointer):
data_pointer.value = opaque
size_pointer.contents.value = sys.maxsize
return 0
def media_read_cb(opaque, buffer, length):
stream = ctypes.cast(opaque, ctypes.py_object).value
new_data = stream.read(length.contents)
buffer.contents.value = new_data
return len(new_data)
def media_seek_cb(opaque, offset):
stream = ctypes.cast(opaque, ctypes.py_object).value
stream.seek(offset)
return 0
def media_close_cb(opaque):
stream = ctypes.cast(opaque, ctypes.py_object).value
stream.close()
callbacks = {
'open': MediaOpenCb(media_open_cb),
'read': MediaReadCb(media_read_cb),
'seek': MediaSeekCb(media_seek_cb),
'close': MediaCloseCb(media_close_cb)
}
def main(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.byref(ctypes.py_object(stream)))
player.set_media(media)
player.play()
while True:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
[02f87cb0] imem demux error: Invalid get/release function pointers
Traceback (most recent call last):
File "_ctypes/callbacks.c", line 234, in 'calling callback function'
File "memory_stream.py", line 21, in media_read_cb
stream = ctypes.cast(opaque, ctypes.py_object).value
ValueError: PyObject is NULL
重复上述回溯,直到我终止程序。
A NoneType 被传递给 media_read_cb,如回溯所示。
代码中的问题似乎是 media_open_cb 函数。
如果您在 media_new_callbacks 函数中将此回调替换为 None,则不会调用它,而会使用适当的不透明指针调用 media_read_cb。
我不太明白其中的原因。如果 open_cb 设置为 None,vlc 将调用其默认值 open_cb,然后默认将 size_pointer 设置为 maxsize,将 data_pointer 设置为不透明(这与您的功能相同)。显然,在设置指针的值时,您的代码中出现了问题。我不知道如何解决这个问题,因为我也是 ctypes 的新手。
当我运行你的代码时:
media = instance.media_new_callbacks(None, callbacks['read'], callbacks['seek'], callbacks['close'], ctypes.byref(ctypes.py_object(stream)))
media_read_cb调用成功。但是,python 然后在以下位置崩溃:
stream = ctypes.cast(opaque, ctypes.py_object).value
我也不知道如何解决这个问题,但有一个解决方法。您可以将流变量设置为全局变量,这样您就可以自己保留指针而不是依赖于 ctypes 的东西。
写入缓冲区似乎也不起作用,因为缓冲区作为字符串传递给 media_read_cb。由于字符串在 python 中是不可变的,因此失败了。一个解决方法是将 CFUNCTYPE 更改为包含 ctypes.POINTER 到 c_char 而不是普通的 c_char_p(python 中的字符串)。然后,您可以通过迭代使用流中的字节填充内存区域。
应用这些更改,您的代码如下所示:
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=None
def media_open_cb(opaque, data_pointer, size_pointer):
data_pointer.value = opaque
size_pointer.contents.value = sys.maxsize
return 0
def media_read_cb(opaque, buffer, length):
new_data = stream.read(length)
for i in range(len(new_data)):
buffer[i]=new_data[i]
return len(new_data)
def media_seek_cb(opaque, offset):
stream.seek(offset)
return 0
def media_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)
}
def main(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()
while True:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
它成功 运行s!
当然,与其使用全局变量,不如将所有这些都包装在一个 python class.
中会更好
编辑: 我想出了如何适当地设置 data_pointer 的方法。这是代码:
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)
def media_open_cb(opaque, data_pointer, size_pointer):
data_pointer.contents.value = opaque
size_pointer.contents.value = sys.maxsize
return 0
def media_read_cb(opaque, buffer, length):
stream=ctypes.cast(opaque,ctypes.POINTER(ctypes.py_object)).contents.value
new_data = stream.read(length)
for i in range(len(new_data)):
buffer[i]=new_data[i]
return len(new_data)
def media_seek_cb(opaque, offset):
stream=ctypes.cast(opaque,ctypes.POINTER(ctypes.py_object)).contents.value
stream.seek(offset)
return 0
def media_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)
}
def main(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()
while True:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
我解决了这个问题,这是完美的方法:
import ctypes
import sys
import time
import os
import vlc
import logging
import ctypes
import threading
class VirFile(object):
def __init__(self, fpath):
self.fpath = fpath
_obj_cache = {}
_obj_lock = threading.Lock()
def cache_object(key: int, *arg):
with _obj_lock:
if key in _obj_cache:
raise RuntimeError(f'cache_object: key duplicate: {key}')
_obj_cache[key] = arg
def cache_remove(key: int):
with _obj_lock:
if key not in _obj_cache:
raise RuntimeError(f'cache_remove: key not found: {key}')
del _obj_cache[key]
@vlc.cb.MediaOpenCb
def media_open_cb(opaque, data_pointer, size_pointer):
try:
vf = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
if not 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 -1
return 0
@vlc.cb.MediaReadCb
def media_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.MediaSeekCb
def media_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 -1
return 0
@vlc.cb.MediaCloseCb
def media_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')
return
def get_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
def main(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)
我正在尝试使用 LibVLC Python 绑定来播放内存中的流(Python 3.4,Windows 7,LibVLC 3.x)。最终,我的目标是将数据馈送到 BytesIO 实例,然后 VLC 将从该实例读取和播放。但目前,我决定破解一个快速脚本来尝试从文件流中读取。这是代码和回溯 - 说我是 ctypes 的新手是一种轻描淡写的说法,所以有人知道我做错了什么吗?
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.c_char_p, 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)
def media_open_cb(opaque, data_pointer, size_pointer):
data_pointer.value = opaque
size_pointer.contents.value = sys.maxsize
return 0
def media_read_cb(opaque, buffer, length):
stream = ctypes.cast(opaque, ctypes.py_object).value
new_data = stream.read(length.contents)
buffer.contents.value = new_data
return len(new_data)
def media_seek_cb(opaque, offset):
stream = ctypes.cast(opaque, ctypes.py_object).value
stream.seek(offset)
return 0
def media_close_cb(opaque):
stream = ctypes.cast(opaque, ctypes.py_object).value
stream.close()
callbacks = {
'open': MediaOpenCb(media_open_cb),
'read': MediaReadCb(media_read_cb),
'seek': MediaSeekCb(media_seek_cb),
'close': MediaCloseCb(media_close_cb)
}
def main(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.byref(ctypes.py_object(stream)))
player.set_media(media)
player.play()
while True:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
[02f87cb0] imem demux error: Invalid get/release function pointers
Traceback (most recent call last):
File "_ctypes/callbacks.c", line 234, in 'calling callback function'
File "memory_stream.py", line 21, in media_read_cb
stream = ctypes.cast(opaque, ctypes.py_object).value
ValueError: PyObject is NULL
重复上述回溯,直到我终止程序。
A NoneType 被传递给 media_read_cb,如回溯所示。 代码中的问题似乎是 media_open_cb 函数。 如果您在 media_new_callbacks 函数中将此回调替换为 None,则不会调用它,而会使用适当的不透明指针调用 media_read_cb。
我不太明白其中的原因。如果 open_cb 设置为 None,vlc 将调用其默认值 open_cb,然后默认将 size_pointer 设置为 maxsize,将 data_pointer 设置为不透明(这与您的功能相同)。显然,在设置指针的值时,您的代码中出现了问题。我不知道如何解决这个问题,因为我也是 ctypes 的新手。
当我运行你的代码时:
media = instance.media_new_callbacks(None, callbacks['read'], callbacks['seek'], callbacks['close'], ctypes.byref(ctypes.py_object(stream)))
media_read_cb调用成功。但是,python 然后在以下位置崩溃:
stream = ctypes.cast(opaque, ctypes.py_object).value
我也不知道如何解决这个问题,但有一个解决方法。您可以将流变量设置为全局变量,这样您就可以自己保留指针而不是依赖于 ctypes 的东西。
写入缓冲区似乎也不起作用,因为缓冲区作为字符串传递给 media_read_cb。由于字符串在 python 中是不可变的,因此失败了。一个解决方法是将 CFUNCTYPE 更改为包含 ctypes.POINTER 到 c_char 而不是普通的 c_char_p(python 中的字符串)。然后,您可以通过迭代使用流中的字节填充内存区域。
应用这些更改,您的代码如下所示:
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=None
def media_open_cb(opaque, data_pointer, size_pointer):
data_pointer.value = opaque
size_pointer.contents.value = sys.maxsize
return 0
def media_read_cb(opaque, buffer, length):
new_data = stream.read(length)
for i in range(len(new_data)):
buffer[i]=new_data[i]
return len(new_data)
def media_seek_cb(opaque, offset):
stream.seek(offset)
return 0
def media_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)
}
def main(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()
while True:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
它成功 运行s!
当然,与其使用全局变量,不如将所有这些都包装在一个 python class.
中会更好编辑: 我想出了如何适当地设置 data_pointer 的方法。这是代码:
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)
def media_open_cb(opaque, data_pointer, size_pointer):
data_pointer.contents.value = opaque
size_pointer.contents.value = sys.maxsize
return 0
def media_read_cb(opaque, buffer, length):
stream=ctypes.cast(opaque,ctypes.POINTER(ctypes.py_object)).contents.value
new_data = stream.read(length)
for i in range(len(new_data)):
buffer[i]=new_data[i]
return len(new_data)
def media_seek_cb(opaque, offset):
stream=ctypes.cast(opaque,ctypes.POINTER(ctypes.py_object)).contents.value
stream.seek(offset)
return 0
def media_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)
}
def main(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()
while True:
time.sleep(1)
if __name__ == '__main__':
try:
path = sys.argv[1]
except IndexError:
print('Usage: {0} <path>'.format(__file__))
sys.exit(1)
main(path)
我解决了这个问题,这是完美的方法:
import ctypes
import sys
import time
import os
import vlc
import logging
import ctypes
import threading
class VirFile(object):
def __init__(self, fpath):
self.fpath = fpath
_obj_cache = {}
_obj_lock = threading.Lock()
def cache_object(key: int, *arg):
with _obj_lock:
if key in _obj_cache:
raise RuntimeError(f'cache_object: key duplicate: {key}')
_obj_cache[key] = arg
def cache_remove(key: int):
with _obj_lock:
if key not in _obj_cache:
raise RuntimeError(f'cache_remove: key not found: {key}')
del _obj_cache[key]
@vlc.cb.MediaOpenCb
def media_open_cb(opaque, data_pointer, size_pointer):
try:
vf = ctypes.cast(opaque, ctypes.POINTER(ctypes.py_object)).contents.value
if not 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 -1
return 0
@vlc.cb.MediaReadCb
def media_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.MediaSeekCb
def media_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 -1
return 0
@vlc.cb.MediaCloseCb
def media_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')
return
def get_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
def main(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)