msdkh264enc: memory leak in case used with dynamic pipelines (Windows)
Using the msdkh264enc element with dynamic pipelines eats my system memory when run on Windows.
Running the following test program, add/remove 'queue ! msdkh264enc ! fakesink' dynamically to/from a running pipeline (derived from dyanamic-tee-vsink example, see [1], [2]), on every loop ca. 20 MB of Memory are leaked (happens only when running on Windows, not on Linux):
#include <gst/gst.h>
#include <stdlib.h>
#define VIDEO_RECORDING_ENCODER "msdkh264enc"
//#define VIDEO_RECORDING_ENCODER "x264enc"
//#define VIDEO_RECORDING_ENCODER "openh264enc"
static GMainLoop* loop;
static GstElement* pipeline;
static GstPad* videoTeeSrcPad;
static GstElement* recordingQueue;
static GstElement* recordingEncoder;
static GstElement* recordingSink;
static volatile int timeoutCbCounter;
static gboolean unlinkCbCalled;
static GstPadProbeReturn unlink_cb(GstPad* pad, GstPadProbeInfo* info, gpointer user_data) {
g_print("Debug: unlink_cb()\n");
if (!g_atomic_int_compare_and_exchange(&unlinkCbCalled, false, true)) {
g_print("Debug: unlink_cb() - removing already set\n");
return GST_PAD_PROBE_OK;
}
GstElement* tee = gst_bin_get_by_name(GST_BIN(pipeline), "VideoTee");
if (tee == nullptr) {
g_print("Error: gst_bin_get_by_name(VideoTee) failed\n");
return GST_PAD_PROBE_OK;
}
GstPad* sinkpad = gst_element_get_static_pad(recordingQueue, "sink");
gst_pad_unlink(videoTeeSrcPad, sinkpad);
gst_object_unref(sinkpad);
gst_bin_remove(GST_BIN(pipeline), recordingQueue);
gst_bin_remove(GST_BIN(pipeline), recordingEncoder);
gst_bin_remove(GST_BIN(pipeline), recordingSink);
gst_element_set_state(recordingSink, GST_STATE_NULL);
gst_element_set_state(recordingEncoder, GST_STATE_NULL);
gst_element_set_state(recordingQueue, GST_STATE_NULL);
#if 0
gst_object_unref(recordingQueue);
recordingQueue = nullptr;
gst_object_unref(recordingEncoder);
recordingEncoder = nullptr;
gst_object_unref(recordingSink);
recordingSink = nullptr;
#endif
gst_element_release_request_pad(tee, videoTeeSrcPad);
gst_object_unref(videoTeeSrcPad);
videoTeeSrcPad = nullptr;
gst_object_unref(tee);
timeoutCbCounter++;
g_print("Debug: unlink done\n");
return GST_PAD_PROBE_REMOVE;
}
static gboolean timeout_cb(gpointer user_data) {
g_print("Debug: timeout_cb() - timeoutCbCounter = %d\n", timeoutCbCounter);
#if 0
// abort after 3 times encoder start/stop
if (timeoutCbCounter >= 6) {
g_print("Debug: timeout_cb() - g_main_loop_quit()\n");
g_main_loop_quit(loop);
return false;
}
#endif
if ((timeoutCbCounter % 2) == 1) {
// unlink the recording branch
unlinkCbCalled = false;
gst_pad_add_probe(videoTeeSrcPad, GST_PAD_PROBE_TYPE_IDLE, unlink_cb, nullptr, nullptr);
return true;
} else {
// add the recoding branch
GstElement* tee = gst_bin_get_by_name(GST_BIN(pipeline), "VideoTee");
if (tee == nullptr) {
g_print("Error: gst_bin_get_by_name(VideoTee) failed\n");
return false;
}
if (recordingQueue == nullptr) {
recordingQueue = gst_element_factory_make("queue", "RecordingQueue");
if (recordingQueue == nullptr) {
g_print("Error: gst_element_factory_make(queue) failed\n");
return false;
}
gst_object_ref_sink(recordingQueue);
}
if (recordingEncoder == nullptr) {
recordingEncoder = gst_element_factory_make(VIDEO_RECORDING_ENCODER, "RecordingEncoder");
if (recordingEncoder == nullptr) {
g_print("Error: gst_element_factory_make(" VIDEO_RECORDING_ENCODER ") failed\n");
return false;
}
gst_object_ref_sink(recordingEncoder);
}
if (recordingSink == nullptr) {
recordingSink = gst_element_factory_make("fakesink", "RecordingSink");
if (recordingSink == nullptr) {
g_print("Error: gst_element_factory_make(fakesink) failed\n");
return false;
}
gst_object_ref_sink(recordingSink);
}
gst_bin_add_many(GST_BIN(pipeline), recordingQueue, recordingEncoder, recordingSink, nullptr);
gst_element_link_many(recordingQueue, recordingEncoder, recordingSink, nullptr);
gst_element_sync_state_with_parent(recordingQueue);
gst_element_sync_state_with_parent(recordingEncoder);
gst_element_sync_state_with_parent(recordingSink);
GstPadTemplate* srcPadTemplate = gst_element_class_get_pad_template(GST_ELEMENT_GET_CLASS(tee), "src_%u");
videoTeeSrcPad = gst_element_request_pad(tee, srcPadTemplate, NULL, NULL);
GstPad* sinkPad = gst_element_get_static_pad(recordingQueue, "sink");
if (gst_pad_link(videoTeeSrcPad, sinkPad) != GST_PAD_LINK_OK) {
g_print("Error: gst_pad_link() failed\n");
gst_object_unref(sinkPad);
gst_element_release_request_pad(tee, videoTeeSrcPad);
gst_object_unref(videoTeeSrcPad);
return false;
}
g_print("Debug: gst_pad_link() queue --> " VIDEO_RECORDING_ENCODER " --> fakesink done\n");
gst_object_unref(sinkPad);
gst_object_unref(tee);
timeoutCbCounter++;
return true;
}
}
int main(int argc, char *argv[]) {
gst_init(&argc, &argv);
loop = g_main_loop_new(nullptr, false);
pipeline = gst_parse_launch("videotestsrc name=VideoSrc ! tee name=VideoTee ! queue name=VideoQueue ! fakesink name=VideoSink", nullptr);
g_return_val_if_fail(pipeline, -1);
g_timeout_add_seconds(3, timeout_cb, loop);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
g_main_loop_run(loop);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
if (recordingQueue != nullptr) {
gst_object_unref(recordingQueue);
}
if (recordingEncoder != nullptr) {
gst_object_unref(recordingEncoder);
}
if (recordingSink != nullptr) {
gst_object_unref(recordingSink);
}
gst_deinit();
exit(0);
}
- OS: Windows 10
- GStreamer: 1.15.2
- Hardware: Intel i7-6920
[1] https://coaxion.net/blog/2014/01/gstreamer-dynamic-pipelines/