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.
|
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)
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(orNDIlib_recv_capture_v2) with a receiver and then the pointers for each frame. You can also supplyNULLif you don't want a frame.However in the code base for
ndi-pythonwe 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
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)
ndi-python/example/recv_cv.py
Lines 37 to 44 in 8aad19d
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)