Skip to content

Potential for memory leak if using the examples with this implementation. #32

Description

@kaine-bruce-dmt

TLDR: The example code is incorrect for the current implementation and does not align with the original NDI spec. This means it is easy to create a memory leak if you're using the example code.

In the NDI SDK documentation, they specifically call NDIlib_recv_capture_v3 (or NDIlib_recv_capture_v2) with a receiver and then the pointers for each frame. You can also supply NULL if you don't want a frame.

However in the code base for ndi-python we specify ALL 3 pointers, which means we need to free all 3 of these pointers.

ndi-python/src/main.cpp

Lines 456 to 488 in 8aad19d

m.def(
"recv_capture_v2",
[](py::capsule instance, uint32_t timeout_in_ms) {
auto p_instance =
static_cast<NDIlib_recv_instance_type *>(instance.get_pointer());
NDIlib_video_frame_v2_t video_frame;
NDIlib_audio_frame_v2_t audio_frame;
NDIlib_metadata_frame_t metadata_frame;
auto type =
NDIlib_recv_capture_v2(p_instance, &video_frame, &audio_frame,
&metadata_frame, timeout_in_ms);
return std::tuple<NDIlib_frame_type_e, NDIlib_video_frame_v2_t,
NDIlib_audio_frame_v2_t, NDIlib_metadata_frame_t>(
type, video_frame, audio_frame, metadata_frame);
},
py::arg("instance"), py::arg("timeout_in_ms"));
m.def(
"recv_capture_v3",
[](py::capsule instance, uint32_t timeout_in_ms) {
auto p_instance =
static_cast<NDIlib_recv_instance_type *>(instance.get_pointer());
NDIlib_video_frame_v2_t video_frame;
NDIlib_audio_frame_v3_t audio_frame;
NDIlib_metadata_frame_t metadata_frame;
auto type =
NDIlib_recv_capture_v3(p_instance, &video_frame, &audio_frame,
&metadata_frame, timeout_in_ms);
return std::tuple<NDIlib_frame_type_e, NDIlib_video_frame_v2_t,
NDIlib_audio_frame_v3_t, NDIlib_metadata_frame_t>(
type, video_frame, audio_frame, metadata_frame);
},
py::arg("instance"), py::arg("timeout_in_ms"));

In the examples folder, we do not explicitly free them, so there is potential for memory leaks if this is running for a long time (I got to 10GB after 17hours while testing)

while True:
t, v, _, _ = ndi.recv_capture_v2(ndi_recv, 5000)
if t == ndi.FRAME_TYPE_VIDEO:
print('Video data received (%dx%d).' % (v.xres, v.yres))
frame = np.copy(v.data)
cv.imshow('ndi image', frame)
ndi.recv_free_video_v2(ndi_recv, v)

Suggest changing the C++ implementation, or changing the examples so that the other 2 frames are freed.

For anyone looking for a quick solution in their own code you can do something like:

(
    frame_type,
    video_frame,
    audio_frame,
    meta_frame,
) = ndi.recv_capture_v3(ndi_connection, 5000)

# As this implementation of NDI implicitly gives you ALL 3 frame types
# You must make sure you free them, else you will get a memory leak!

if frame_type == ndi.FRAME_TYPE_VIDEO:
    # do stuff
    ndi.recv_free_video_v2(ndi_connection, video_frame)

if frame_type == ndi.FRAME_TYPE_AUDIO:
     # do stuff
    ndi.recv_free_audio_v3(ndi_connection, audio_frame)

if frame_type == ndi.FRAME_TYPE_METADATA:
     # do stuff
    ndi.recv_free_metadata(ndi_connection, meta_frame)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions