[gst-devel] Question about gst_structure_get_name()

W. Michael Petullo mike at flyn.org
Tue May 18 18:43:19 CEST 2010


I am using gst_structure_get_name() to determine the mimetype of a
stream (along with other things). In fact, I am doing this a few thousand
times, each time setting up and tearing down a pipeline that makes use of
decodebin. Because gst_structure_get_name() returns a constant string,
I thought that it was wrong to free the returned string. However, I am
finding that not free'ing the returned string results in what appears
to be a memory leak. Approximately 5-6MB of memory is consumed
after processing 2,419 media files. Free'ing the string returned by
gst_structure_get_name() results in this memory not being consumed.

Can someone comment on the correct use of gst_structure_get_name()? Or
do these symptoms indicate a problem elsewhere? I have attached my C
source for reference.

-- 
Mike

:wq
-------------- next part --------------
/*
 * Utility functions implemented using GStreamer
 *
 * Copyright (C) 2009 W. Michael Petullo <mike at flyn.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <string.h>
#include <gst/gst.h>

#include "dmapd-daap-record.h"
#include "av-meta-reader-gst.h"

static GOnce once = G_ONCE_INIT;

struct AVMetaReaderGstPrivate {
	GMutex *tag_read;
	GstElement *pipeline;
	GstElement *src;
	GstElement *decode;
	GstElement *typefind;
	gboolean has_video;
};

static void
av_meta_reader_gst_set_property (GObject *object,
				 guint prop_id,
				 const GValue *value,
				 GParamSpec *pspec)
{
	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}

static void
av_meta_reader_gst_get_property (GObject *object,
				 guint prop_id,
				 GValue *value,
				 GParamSpec *pspec)
{
	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}

static GOptionGroup *
av_meta_reader_gst_get_option_group (AVMetaReader *reader)
{
        return gst_init_get_option_group ();
}

static void
av_meta_reader_gst_finalize (AVMetaReaderGst *self)
{
	//G_OBJECT_CLASS (av_meta_reader_gst_parent_class)->finalize (object);
}

static void
av_meta_reader_gst_class_finalize (AVMetaReaderGstClass *klass)
{
}

static gboolean
message_loop (GstElement *element, GstTagList **tags)
{
	GstBus *bus;
	gboolean done = FALSE;

	bus = gst_element_get_bus (element);
	g_return_val_if_fail (bus != NULL, FALSE);
	g_return_val_if_fail (tags != NULL, FALSE);

	while (! done) {
		GstMessage *message;

		message = gst_bus_pop (bus);

		if (message == NULL)
			/* All messages read, we're done */
			break;

		switch (GST_MESSAGE_TYPE (message)) {
		case GST_MESSAGE_TAG: {
			GstTagList *new_tags, *old_tags;

			gst_message_parse_tag (message, &new_tags);
			if (*tags) {
				old_tags = *tags;
				*tags = gst_tag_list_merge (old_tags,
							    new_tags,
							    GST_TAG_MERGE_KEEP);
				gst_tag_list_free (old_tags);
				gst_tag_list_free (new_tags);
			} else
				*tags = new_tags;
			break;
		}
		default:
			break;
		}
		gst_message_unref (message);
	}

	gst_object_unref (bus);

	return TRUE;
}

static gchar *
determine_format (DAAPRecord *record, const gchar *description)
{
	gchar *format;

	if (g_strrstr (description, "MP3"))
		format = "mp3";
	else if (g_strrstr (description, "MPEG-4 AAC"))
		format = "aac";
	else if (g_strrstr (description, "Vorbis"))
		format = "ogg";
	else if (g_strrstr (description, "FLAC"))
		format = "flac";
	else {
		gchar *ext, *location;

		g_debug ("Could not determine type from stream, using filename extension");
		g_object_get (record, "location", &location, NULL);
		ext = strrchr (location, '.');
		if (ext == NULL) {
			ext = "mp3";
		} else {
			ext++;
		}

		format = ext;
	}

	g_debug ("Format is %s", format);
	return format;
}


static void
insert_tag (const GstTagList * list, const gchar * tag, DAAPRecord *record)
{
	gint i, count;

	if (tag == NULL)
		return;

	count = gst_tag_list_get_tag_size (list, tag);

	for (i = 0; i < count; i++) {
		gchar *val;

		if (gst_tag_get_type (tag) == G_TYPE_STRING) {
			if (!gst_tag_list_get_string_index (list, tag, i, &val))
				g_assert_not_reached ();
		} else {
			val = g_strdup_value_contents (gst_tag_list_get_value_index (list, tag, i));
		}

		g_debug ("%s is %s", tag, val);
		if (! strcmp ("title", tag)) {
			g_object_set (record, "title", val, NULL);
		} else if (! strcmp ("artist", tag)) {
			g_object_set (record, "artist", val, NULL);
		} else if (! strcmp ("album", tag)) {
			g_object_set (record, "album", val, NULL);
		} else if (! strcmp ("genre", tag)) {
			g_object_set (record, "genre", val, NULL);
		} else if (! strcmp ("audio-codec", tag)) {
			gboolean has_video;
			g_debug ("%s video", has_video ? "Has" : "Does not have");
			g_object_set (record, "real-format", determine_format (record, val), NULL);
			/* Determine the format to "advertise" (i.e., used for 
			 * URL path)
			 */
			g_object_get (record, "has-video", &has_video, NULL);
			if (has_video) {
				/* FIXME: get from video stream. */
				gchar *ext, *location;

				g_object_get (record, "location", &location, NULL);
				ext = strrchr (location, '.');
				if (ext == NULL) {
					ext = "mov";
				} else {
					ext++;
				}
				g_object_set (record, "format", ext, NULL);
			} else {
				g_object_set (record, "format", determine_format (record, val), NULL);
			}
		} else if (! strcmp ("track-number", tag)) {
			g_object_set (record, "track", atoi (val), NULL);
		} else {
			/* Unused. */
			g_debug ("Unused metadata");
		}
		g_free (val);
	}
}

static void
setup_mutex (gpointer user_data)
{
	AV_META_READER_GST (user_data)->priv->tag_read = g_mutex_new ();
}

static gboolean
setup_pipeline (AVMetaReaderGst *reader)
{
	/* Set up pipeline. */
	reader->priv->pipeline = gst_pipeline_new ("pipeline");

	g_debug ("Make filesrc");
	reader->priv->src = gst_element_factory_make ("filesrc", "source");
	if (reader->priv->src == NULL) {
		g_warning ("Error creating filesrc element");
		return FALSE;
	}

	g_debug ("Make decodebin");
	reader->priv->decode = gst_element_factory_make ("decodebin", "decoder");
	if (reader->priv->decode == NULL) {
		g_warning ("Error creating decodebin element");
		return FALSE;
	}

	g_debug ("Make typefind");
	reader->priv->typefind = gst_element_factory_make (
						"typefind", "typefind");
	if (reader->priv->typefind == NULL) {
		g_warning ("Error creating typefind element");
		return FALSE;
	}

	gst_bin_add_many (GST_BIN (reader->priv->pipeline),
			  reader->priv->src,
			  reader->priv->decode,
			  reader->priv->typefind,
			  NULL);
	if (gst_element_link (reader->priv->src, reader->priv->decode) == FALSE) {
		g_warning ("Error linking pipeline");
		return FALSE;
	}

	g_debug ("Pipeline complete");
	return TRUE;
}

static gboolean
pause_pipeline (GstElement *pipeline)
{
	GstState state;
	GstStateChangeReturn sret;

	sret = gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
	if (GST_STATE_CHANGE_ASYNC == sret) {
		if (GST_STATE_CHANGE_SUCCESS != gst_element_get_state (GST_ELEMENT (pipeline), &state, NULL, 1 * GST_SECOND)) {
			g_warning ("State change failed");
		}
	} else if (sret != GST_STATE_CHANGE_SUCCESS) {
		g_print ("Could not read file");
	}

	/* Run once. */
	return FALSE;
}

static gboolean
quit_mainloop (GMainLoop *loop)
{
	g_main_loop_quit (loop);

	/* Run once. */
	return FALSE;
}

static void
no_more_pads_cb (GstElement *element, GMainLoop *loop)
{
	quit_mainloop (loop);	
}

/* FIXME: copied from libdmapsharing: */
gboolean
pads_compatible (GstPad *pad1, GstPad *pad2)
{
        gboolean fnval;
        GstCaps *res, *caps1, *caps2;

        caps1 = gst_pad_get_caps (pad1);
        caps2 = gst_pad_get_caps (pad2);
        res = gst_caps_intersect (caps1, caps2);
        fnval = res && ! gst_caps_is_empty (res);

        gst_caps_unref (res);
        gst_caps_unref (caps2);
        gst_caps_unref (caps1);

        return fnval;
}

static void
newpad_cb (GstElement *decodebin, GstPad *pad, gboolean last, AVMetaReaderGstPrivate *priv)
{
	GstCaps *caps;
	const gchar *mimetype;
	GstStructure *structure;

	/* Get mimetype. */
	caps = gst_pad_get_caps (pad);
	if (gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
		g_warning ("Error getting caps from pad");
		goto _return;
	}

	structure = gst_caps_get_structure (caps, 0);
	mimetype = gst_structure_get_name (structure);

	g_debug ("Pad mimetype is %s.", mimetype);

	if (g_strrstr (mimetype, "video")) {
		priv->has_video |= TRUE;
	} else if (g_strrstr (mimetype, "audio")) {
		GstPad *typefind_pad;

		typefind_pad = gst_element_get_static_pad (priv->typefind, "sink");
		g_assert (typefind_pad != NULL);

		if (pads_compatible (pad, typefind_pad)) {
			g_assert (! GST_PAD_IS_LINKED (
				gst_element_get_static_pad (priv->typefind, "sink")));
			gst_pad_link (pad, typefind_pad);
		}
		gst_object_unref (typefind_pad);
	}

	/* FIXME: mimetype is a const. However, if I do not free it, dmapd
	 * seems to leak memory (~5MB with my music library).
	 */
	g_free (mimetype);
_return:
	gst_caps_unref (caps);
}

static gboolean
av_meta_reader_gst_read (AVMetaReader *reader, DAAPRecord *record, const gchar *path)
{
	GstFormat fmt;
	GstTagList *tags = NULL;
	gint64 nanoduration;
	gboolean has_video = FALSE;
	GMainLoop *loop;
	AVMetaReaderGst *gst_reader = AV_META_READER_GST (reader);	

	g_once (&once, (GThreadFunc) setup_mutex, gst_reader);

	loop = g_main_loop_new (NULL, FALSE);
	g_mutex_lock (gst_reader->priv->tag_read);

	if (! setup_pipeline (gst_reader))
		goto _return;

	g_object_set (G_OBJECT (gst_reader->priv->src), "location", path, NULL);

	/* Connect callback to identify audio and/or video tracks. */
	g_signal_connect (gst_reader->priv->decode,
			  "new-decoded-pad",
			  G_CALLBACK (newpad_cb),
			  gst_reader->priv);

	g_signal_connect (gst_reader->priv->decode,
			  "no-more-pads",
			  G_CALLBACK (no_more_pads_cb),
			  loop);

	/* Run main loop to allow decodebin to create pads. Quit on
	 * "no-more-pads" signal. */
	g_idle_add ((GSourceFunc) pause_pipeline, gst_reader->priv->pipeline);
	g_timeout_add_seconds (1, (GSourceFunc) quit_mainloop, loop);
	g_main_loop_run (loop);

	fmt = GST_FORMAT_TIME;

	if (! gst_element_query_duration (gst_reader->priv->typefind,
					 &fmt,
					 &nanoduration)) {
		g_warning ("Could not determine duration of %s", path);
	} else {
		/* NOTE: cast avoids segfault on MIPS32: */
		g_object_set (record, "duration", (gint32) (nanoduration / GST_SECOND), NULL);
	}

	if (!message_loop (GST_ELEMENT (gst_reader->priv->pipeline), &tags)) {
		g_warning ("Failed in message reading for %s\n", path);
	}

	/* NOTE: Must set has_video before calling insert_tag. */
	g_object_set (record, "has-video", has_video, NULL);

	if (tags) {
		gst_tag_list_foreach (tags, (GstTagForeachFunc) insert_tag, record);
		gst_tag_list_free (tags);
		tags = NULL;
	} else
		g_warning ("No metadata found for %s\n", path);

_return:
	gst_element_set_state (gst_reader->priv->pipeline, GST_STATE_NULL);
	g_object_unref (gst_reader->priv->pipeline);

	g_mutex_unlock (gst_reader->priv->tag_read);

	// g_main_loop_unref (loop);

	return TRUE;
}

static void av_meta_reader_gst_register_type (GTypeModule *module);

G_MODULE_EXPORT gboolean
dmapd_module_load (GTypeModule *module)
{
	av_meta_reader_gst_register_type (module);
}

G_MODULE_EXPORT gboolean
dmapd_module_unload (GTypeModule *module)
{
}

static void av_meta_reader_gst_init (AVMetaReaderGst *reader)
{
	reader->priv = AV_META_READER_GST_GET_PRIVATE (reader);
}

static void av_meta_reader_gst_class_init (AVMetaReaderGstClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	AVMetaReaderClass *av_meta_reader_class = AV_META_READER_CLASS (klass);

	g_type_class_add_private (klass, sizeof (AVMetaReaderGstPrivate));

	gobject_class->set_property = av_meta_reader_gst_set_property;
	gobject_class->get_property = av_meta_reader_gst_get_property;
	gobject_class->finalize = av_meta_reader_gst_finalize;

	av_meta_reader_class->read = av_meta_reader_gst_read;
	av_meta_reader_class->get_option_group = av_meta_reader_gst_get_option_group;
}

G_DEFINE_DYNAMIC_TYPE (AVMetaReaderGst,
		       av_meta_reader_gst,
		       TYPE_AV_META_READER)


More information about the gstreamer-devel mailing list