Skip to content Skip to sidebar Skip to footer

Passing File-like Objects To Ctypes Callbacks

I'm attempting to use the LibVLC Python bindings to play an in-memory stream (Python 3.4, Windows 7, LibVLC 3.x). Eventually, my aim is to feed data into a BytesIO instance which

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"