oaubert / python-vlc

Python vlc bindings
GNU Lesser General Public License v2.1
381 stars 108 forks source link

SegFault when returning `media` object from function #227

Closed gulaki closed 1 year ago

gulaki commented 1 year ago

I am constructing some MIDI data as a io.BytesIO object in memory. To load these into VLC as media for play I adapted the media callback functions adapted from the provided examples from the repo. In this example, I am just reading a midi file on disk and writing the data into a BytesIO object.

import io
import time
import vlc

@vlc.CallbackDecorators.MediaOpenCb
def media_open_cb(opaque, data_pointer, size_pointer):
    print("OPEN", opaque, data_pointer, size_pointer)
    stream_provider = vlc.ctypes.cast(opaque, vlc.ctypes.POINTER(vlc.ctypes.py_object)).contents.value
    stream_provider.seek(0)
    data_pointer.contents.value = opaque
    size_pointer.value = 1 ** 64 - 1
    return 0

@vlc.CallbackDecorators.MediaReadCb
def media_read_cb(opaque, buffer, length):
    print("READ", opaque, buffer, length)
    stream_provider = vlc.ctypes.cast(opaque, vlc.ctypes.POINTER(vlc.ctypes.py_object)).contents.value
    new_data = stream_provider.read()
    print(new_data, type(new_data))
    bytes_read = len(new_data)

    if bytes_read > 0:
        buffer_array = vlc.ctypes.cast(buffer, vlc.ctypes.POINTER(vlc.ctypes.c_char * bytes_read))
        for index, b in enumerate(new_data):
            buffer_array.contents[index] = vlc.ctypes.c_char(b)
        print(type(buffer_array.contents))

    print(f"just read f{bytes_read}B")
    return bytes_read

@vlc.CallbackDecorators.MediaSeekCb
def media_seek_cb(opaque, offset):
    print("SEEK", opaque, offset)
    stream_provider = vlc.ctypes.cast(opaque, vlc.ctypes.POINTER(vlc.ctypes.py_object)).contents.value
    stream_provider.seek(offset)

    return 0

@vlc.CallbackDecorators.MediaCloseCb
def media_close_cb(opaque):
    print("CLOSE", opaque)
    stream_provider = vlc.ctypes.cast(opaque, vlc.ctypes.POINTER(vlc.ctypes.py_object)).contents.value
    stream_provider.close()

Then I have the vlc instance and player in the global scope

instance = vlc.Instance('ml')
player = instance.media_player_new()

I am loading the MIDI data into an io.BytesIO object with the following code.

with open('drums.mid, 'rb') as f:
    a = io.BytesIO()
    a.write(f.read())

Then setting up the callbacks to the new media type

stream_provider_obj = vlc.ctypes.py_object(a)
stream_provider_ptr = vlc.ctypes.byref(stream_provider_obj)

media = instance.media_new_callbacks(
        media_open_cb,
        media_read_cb,
        media_seek_cb,
        media_close_cb,
        stream_provider_ptr
)
player.set_media(media)
player.play()
time.sleep(100)

This works well and the midi file plays. However, I actually need to play various MIDI data constructed in memory, so I pass the BytesIO object to a function to return the media object, that I then add to the player.

def get_bytes_media(m):
    stream_provider_obj = vlc.ctypes.py_object(m)
    stream_provider_ptr = vlc.ctypes.byref(stream_provider_obj)

    media = instance.media_new_callbacks(
        media_open_cb,
        media_read_cb,
        media_seek_cb,
        media_close_cb,
        stream_provider_ptr
    )
    return media

media = get_bytes_media(a)
player.set_media(media)
player.play()
time.sleep()

This does not work and I get a SEGFAULT as

OPEN 140693869246992 <vlc.LP_c_void_p object at 0x7ff5d81939c0> <vlc.LP_c_ulong object at 0x7ff5d8193ac0>
[1]    20755 segmentation fault  python play_bytesIO.py

If instead of returning the media, I set_media and play inside the function, then I get

OPEN 140651341330832 <vlc.LP_c_void_p object at 0x7febf13cb9c0> <vlc.LP_c_ulong object at 0x7febf13cb940>
[1]    20824 bus error  python play_bytesIO.py

I also tried media_open_fd thinking BytesIO are file like objects. But that doesn't work either. What is the correct way to handle this problem?

gulaki commented 1 year ago

After doing some research into ctypes see this stakoverflow question, this is what worked

class VlcBytes:
    def __init__(self):
        self._instance = vlc.Instance()
        self._player = self._instance.media_player_new()
        self._bytesio = None
        self._media = None

        self._stream_provider_obj = None
        self._stream_provider_ptr = None

    def play(self):
        self._player.play()

    def pause(self):
        self._player.pause()

    def load_bytes_media(self, bytesio):
        self._bytesio = bytesio
        self._stream_provider_obj = vlc.ctypes.py_object(self._bytesio)
        self._stream_provider_ptr = vlc.ctypes.byref(self._stream_provider_obj)

        self._media = self._instance.media_new_callbacks(
            media_open_cb,
            media_read_cb,
            media_seek_cb,
            media_close_cb,
            self._stream_provider_ptr
        )
        self._player.set_media(self._media)

    def load_mrl_media(self, filename):
        self._media = self._instance.media_new(filename)
        self._player.set_media(self._media)

vlcp = VlcBytes()
vlcp.load_mrl_media('drums.mid')
vlcp.play()
time.sleep(100)

This issue can be closed after moderator acknowledgement.

oaubert commented 1 year ago

Thanks for documenting the issue and the solution. If you manage to make a simple example file, it could be put in the examples directory of the python-vlc sources, so that it is more easily findable by other people.