Sergei Vorobyov <sergei.vorobyov@facilitylabs.com>
5:52 pm (5 days ago)

to gwenole.beauch., jonathan.bian, shengquan.yuan, bcc: Sergei 
Greetings!

I decided to contact you since I found your names on vaapisink at freedesktop and through gst-inspect. I do not know whether you are still active or doing it as your hobby or work. Sorry anyway. Let me know, if you can, more appropriate persons to contact on that matter. GStreamer guys say vaapisink is not their business.

I think there is a serious memory leak on vaapisink. Below you will find a simple full program that plays image files from  a directory through a simple pipeline iteratively substituting new locations as a property of the filesrc element.

My reasoning is simple:

1. if I run this program with ximagesink, it works for days without leaking any memory (stable at 0.1%-0.2%), however,

2. if I run it with vaapisink, it quickly eats up more and more memory (as can be observed by top or htop) an eventually (after 2-4 hours in my case) is killed by the kernel.

This behavior is systematically observed on:

1. Intel HD 4400 IGP, with the 01.org driver for Ubuntu 14.04 LTS and video aceleration
2. AMD Radeon with XvBA
3. NVidia with VDPAU

Since vaapisink is the "greatest common divisor" of the three (with drivers developed by 3 independent groups), it's fair to conclude that it's vaapisink which leaks memory.

The program below can be run as

test <sink>

where <sink> is the desired sink factory name, like "ximagesink", "vaapisink", "autovideosink", ...

If you run just

test

it will default to "autovideosink".

You just need to specify the #define MEDIA_DIR constant where you keep arbitrary image files *.png or *.jpg. ANy files will do (one or more)

The program behaves similarly with movies *.avi, *.mpg, *mp4, ... if you delete imagefreeze and link (similarly) decodebin to videoconvert (instead of imagefreeze).


/*
 * Adjust the MEDIA_DIR constant as explained below
 * Compile with:
 * gcc -std=c11 -Wall -pthread test.c -o test `pkg-config --cflags --libs glib-2.0 gstreamer-1.0 gstreamer-pbutils-1.0 gstreamer-video-1.0`
 * Run, giving a sink factory name as an argument, like "test vaapisink" and enjoy
 */
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <gst/gst.h>
/*
 * This program implements cyclically playing all image files in MEDIA_DIR
 * through the pipeline
 * filesrc location=... ! decodebin ! imagefreeze ! videoconvert | <sink>
 * by setting the location property to different files in MEDIA_DIR
 */
// Modify as needed. Directory should contain only image files: .jpg, .png, ..., one or more.
#define MEDIA_DIR "/home/fl/ads"

static GstBus *bus;
static GstMessage *message;
static GstElement *pipeline, *filesrc, *decodebin, *videoconvert, *imagefreeze, *sink;
static gchar const *videosink;

static void dynamic_pad_link_cb (GstElement *decbin, GstPad *srcpad, gpointer imgfreeze)  {
    gchar *pad_name = gst_pad_get_name (srcpad);
    g_print ("dynamically linking %s on pad %s to %s ...\n", 
            GST_OBJECT_NAME (decbin), pad_name, GST_OBJECT_NAME (imgfreeze));
    g_free (pad_name);
    GstCaps *caps = gst_pad_query_caps (srcpad, NULL);
    GstPad *sinkpad = gst_element_get_compatible_pad ((GstElement*) imgfreeze, srcpad, caps);
    GstPadLinkReturn pad_link_outcome =  gst_pad_link (srcpad, sinkpad);
    if (pad_link_outcome == GST_PAD_LINK_OK)
	g_print ("dynamic link succeeded!\n");
    else {
	g_print ("dynamic link failed with code %d\n", pad_link_outcome);
        exit (-1);
    }
    gst_object_unref (sinkpad);
    gst_caps_unref (caps);
}

static void build_pipeline () {
    pipeline = gst_pipeline_new ("main_pipeline");
    filesrc = gst_element_factory_make ("filesrc", "filesrc");
    decodebin = gst_element_factory_make ("decodebin", "decodebin");
    videoconvert = gst_element_factory_make ("videoconvert", "videoconvert");
    imagefreeze = gst_element_factory_make ("imagefreeze", "imagefreeze");
    sink = gst_element_factory_make (videosink, videosink);
    if (!sink) {
        g_print ("failed to create %s\n", videosink);
        exit (-1);
    }
    gst_bin_add_many (GST_BIN (pipeline), filesrc, decodebin, imagefreeze, videoconvert, sink, NULL);
    gst_element_link (filesrc, decodebin);
    g_signal_connect (decodebin, // instance to connect signal to
		      "pad-added", // detailed signal
		      G_CALLBACK (dynamic_pad_link_cb), // assoc callback
		      imagefreeze); // passed as the 3rd arg to the callback
    gst_element_link (imagefreeze, videoconvert);
    gst_element_link (videoconvert, sink);
    bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
}

static void play_media_file (gchar const *fname) {
    gchar *full_file_name = g_strdup_printf (MEDIA_DIR G_DIR_SEPARATOR_S "%s", fname);
    g_object_set (filesrc, "location", full_file_name, NULL);
    g_print ("playing %s\n", full_file_name);
    if (gst_element_set_state (pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
        g_print ("failed to set pipeline to PLAYING\n");
        exit (-1);
    }
    message = gst_bus_timed_pop_filtered (bus, 5 * GST_SECOND,
                                      (GstMessageType)(GST_MESSAGE_EOS |
                                                       GST_MESSAGE_SEGMENT_DONE |
                                                       GST_MESSAGE_ERROR));
    if (message == NULL)
        g_print ("play time fully expired!\n");
    else {
        GstMessageType msg_type = GST_MESSAGE_TYPE (message);
        gchar const *name = gst_message_type_get_name (msg_type); // a reference to the static name of the message, don't free
        g_print ("received message type=%d, name=%s\n", msg_type, name);
        if (msg_type & GST_MESSAGE_EOS)
            g_print ("GST_MESSAGE_EOS\n");
        else if (msg_type & GST_MESSAGE_ERROR)
            g_print ("GST_MESSAGE_ERROR, bad %s\n", full_file_name);
        else
            g_print ("UNEXPECTED message type %s\n", name);
        gst_message_unref (message); // only when != NULL !!!
        message = NULL;
    }
    if (gst_element_set_state (pipeline, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) {
        g_print ("failed to set pipeline to state READY\n");
        exit (-1);
    }
    g_free (full_file_name);
    gst_element_unlink (decodebin, imagefreeze); // dynamically linked each time, independently of this unlink
}

gint main (gint argc, gchar** argv) {
    gst_init (&argc, &argv);
    guint major, minor, micro, nano;
    gst_version (&major, &minor, &micro, &nano);
    g_print ("GStreamer v%d.%d.%d.%d\n", major, minor, micro, nano);
    videosink = (argc > 1)? argv[1] : "autovideosink";
    g_print ("using %s\n", videosink);
    build_pipeline ();
    for (gint i=1; ; i++) {
        g_print ("*** Cycle #%d through all media files in %s\n", i, MEDIA_DIR);
        GDir *dir = g_dir_open (MEDIA_DIR, 0, NULL);
        gchar const *file_name;
        while ((file_name = g_dir_read_name(dir))) // file_name must not be modified or freed
            play_media_file (file_name);
        g_dir_close(dir);
    }
}

Sergei Vorobyov <sergei.vorobyov@facilitylabs.com>
5:57 pm (5 days ago)

to gwenole.beauch., jonathan.bian, shengquan.yuan 
Forgot to add that this behavior concerns GStreamer v1.2.4.0 (which comes with Ubuntu 14.04 LTS).

Tomorrow I'll test it with GStreamer v1.4.3 (which comes with Ubuntu 14.10). As I remember it's all the same, but I'll double check with AMD Radeon and NVidia