2

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 VLC will then read from and play. But for the moment, I decided to hack up a quick script to try reading from a file stream. Here's the code and traceback - to say I'm pretty new to ctypes would be an understatement so does anyone know what I'm doing wrong?

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

The above traceback is repeated until I kill the program.

James Scholes
  • 7,686
  • 3
  • 19
  • 20

2 Answers2

7

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=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)

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)


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)
Munchhausen
  • 442
  • 4
  • 16
  • I think "thank you!" comments are generally frouned upon around here, but this is awesome. I had given up hope that I would ever figure this out or that this would ever be answered. I'm gonna wrap this up into a class and try using it to load a queue of HLS chunks. If I could upvote you more than once, I would. – James Scholes Jul 28 '16 at 16:55
  • You are welcome. I stumbled upon this question because I had a similar issue. Please also note that libvlc does not like to receive fewer bytes than it asked for. So, waiting for more data in the media_read_cb function is a good approach (I thought it would crash things). – Munchhausen Jul 28 '16 at 19:20
  • Any idea why I'm consistenytly seeing the error: `[03527320] imem demux error: Invalid get/release function pointers` when starting playback? The media still plays fine. – James Scholes Jul 29 '16 at 14:02
  • No idea! I have that too with my own implementation. Maybe the errors are still there because of the migration from the old imem method (command line) to this new method. – Munchhausen Jul 29 '16 at 18:02
  • How can i make the contrary? Instead of playing audio normaly how i would save the vlc audio output to a BytesIO instead of playing it? – lithiumlab Sep 09 '16 at 19:47
  • 1
    @lithiumlab Use smem instead of imem. Currently, you can use callbacks for similar behavior: http://stackoverflow.com/questions/32825363/working-with-vlc-smem legacy method: https://wiki.videolan.org/Stream_to_memory_(smem)_tutorial/ – Munchhausen Sep 09 '16 at 20:45
  • @Munchhausen thanks for the references. are any examples available in python? on of the answers suggests to interact directly with the MediaPlayer class. i'm not sure if this is still true for python. – lithiumlab Sep 09 '16 at 23:59
  • @lithiumlab No there are no examples I know of! Maybe there are not many users using this. It is still true for python, see this for the docs: https://www.olivieraubert.net/vlc/python-ctypes/doc/vlc-module.html#libvlc_video_set_callbacks – Munchhausen Sep 10 '16 at 10:41
  • Thanks for the answer! – Chris P Aug 20 '20 at 16:28
  • @Munchhausen James sir I am also trying to do the same thing, By readin a file into BytesIO and then playing it, but I am not able to play BytesIO file, How can I do it – Vinay Kumar Jan 25 '21 at 16:07
-1

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


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)
Samwell Zuk
  • 1
  • 1
  • 2
  • after 4-5 seconds this crashes saying ``` [00007f55f806e1d0] xcb_window window error: X server failure [00007fea8c002710] imem demux error: Invalid get/release function pointers``` – Vinay Kumar Jan 25 '21 at 16:04