将类文件对象传递给ctypes回调

时间:2016-02-26 22:20:44

标签: python python-3.x libvlc

我试图使用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

重复上述追溯,直到我杀死该程序。

2 个答案:

答案 0 :(得分:1)

如traceback所示,将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设置为opaque(与您的函数相同)。显然,在设置指针值时,代码中的某些内容会出错。我不知道如何解决这个问题,因为我也是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而不是plain 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)

它成功运行了!

当然,不是使用全局变量,最好将所有这些包装在python类中。

编辑:我想出了如何适当地设置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)

答案 1 :(得分:0)

我解决了这个问题,这是完美的方法:

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)