2014-10-21 08:35:48 +00:00
|
|
|
#include "common.h"
|
|
|
|
|
|
|
|
void
|
|
|
|
poll_the_bus (GstBus * bus)
|
|
|
|
{
|
|
|
|
GstMessage *message;
|
|
|
|
gboolean carry_on = TRUE;
|
|
|
|
|
|
|
|
while (carry_on) {
|
|
|
|
message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
|
|
|
|
if (message) {
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
|
|
case GST_MESSAGE_EOS:
|
|
|
|
/* we should check if we really finished here */
|
|
|
|
GST_DEBUG ("Got an EOS");
|
|
|
|
carry_on = FALSE;
|
|
|
|
break;
|
|
|
|
case GST_MESSAGE_SEGMENT_START:
|
|
|
|
case GST_MESSAGE_SEGMENT_DONE:
|
|
|
|
/* We shouldn't see any segement messages, since we didn't do a segment seek */
|
|
|
|
GST_WARNING ("Saw a Segment start/stop");
|
|
|
|
fail_if (TRUE);
|
|
|
|
break;
|
|
|
|
case GST_MESSAGE_ERROR:
|
|
|
|
fail_error_message (message);
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
gst_mini_object_unref (GST_MINI_OBJECT (message));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-25 09:03:12 +00:00
|
|
|
static gboolean
|
|
|
|
nle_object_commit (GstElement * nlesource, gboolean recurse)
|
|
|
|
{
|
|
|
|
gboolean ret;
|
|
|
|
|
|
|
|
g_signal_emit_by_name (nlesource, "commit", recurse, &ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-10-21 08:35:48 +00:00
|
|
|
GstElement *
|
|
|
|
gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name)
|
|
|
|
{
|
|
|
|
GstElement *element;
|
|
|
|
|
|
|
|
element = gst_element_factory_make (factoryname, name);
|
|
|
|
fail_unless (element != NULL, "Failed to make element %s", factoryname);
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
composition_pad_added_cb (GstElement * composition, GstPad * pad,
|
|
|
|
CollectStructure * collect)
|
|
|
|
{
|
|
|
|
fail_if (!(gst_element_link_pads_full (composition, GST_OBJECT_NAME (pad),
|
|
|
|
collect->sink, "sink", GST_PAD_LINK_CHECK_NOTHING)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return TRUE to discard the Segment */
|
|
|
|
static gboolean
|
|
|
|
compare_segments (CollectStructure * collect, Segment * segment,
|
|
|
|
GstEvent * event)
|
|
|
|
{
|
2014-07-03 12:34:11 +00:00
|
|
|
const GstSegment *received_segment;
|
2014-10-21 08:35:48 +00:00
|
|
|
guint64 running_stop, running_start, running_duration;
|
|
|
|
|
2014-07-03 12:34:11 +00:00
|
|
|
gst_event_parse_segment (event, &received_segment);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
|
|
|
GST_DEBUG ("Got Segment rate:%f, format:%s, start:%" GST_TIME_FORMAT
|
|
|
|
", stop:%" GST_TIME_FORMAT ", time:%" GST_TIME_FORMAT
|
|
|
|
", base:%" GST_TIME_FORMAT ", offset:%" GST_TIME_FORMAT,
|
2014-07-03 12:34:11 +00:00
|
|
|
received_segment->rate, gst_format_get_name (received_segment->format),
|
|
|
|
GST_TIME_ARGS (received_segment->start),
|
|
|
|
GST_TIME_ARGS (received_segment->stop),
|
|
|
|
GST_TIME_ARGS (received_segment->time),
|
|
|
|
GST_TIME_ARGS (received_segment->base),
|
|
|
|
GST_TIME_ARGS (received_segment->offset));
|
2014-10-21 08:35:48 +00:00
|
|
|
GST_DEBUG ("[RUNNING] start:%" GST_TIME_FORMAT " [STREAM] start:%"
|
2014-07-03 12:34:11 +00:00
|
|
|
GST_TIME_FORMAT,
|
|
|
|
GST_TIME_ARGS (gst_segment_to_running_time (received_segment,
|
|
|
|
GST_FORMAT_TIME, received_segment->start)),
|
|
|
|
GST_TIME_ARGS (gst_segment_to_stream_time (received_segment,
|
|
|
|
GST_FORMAT_TIME, received_segment->start)));
|
2014-10-21 08:35:48 +00:00
|
|
|
|
|
|
|
GST_DEBUG ("Expecting rate:%f, format:%s, start:%" GST_TIME_FORMAT
|
|
|
|
", stop:%" GST_TIME_FORMAT ", position:%" GST_TIME_FORMAT ", base:%"
|
|
|
|
GST_TIME_FORMAT, segment->rate, gst_format_get_name (segment->format),
|
|
|
|
GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop),
|
|
|
|
GST_TIME_ARGS (segment->position),
|
|
|
|
GST_TIME_ARGS (collect->expected_base));
|
|
|
|
|
|
|
|
running_start =
|
2014-07-03 12:34:11 +00:00
|
|
|
gst_segment_to_running_time (received_segment, GST_FORMAT_TIME,
|
|
|
|
received_segment->start);
|
2014-10-21 08:35:48 +00:00
|
|
|
running_stop =
|
2014-07-03 12:34:11 +00:00
|
|
|
gst_segment_to_running_time (received_segment, GST_FORMAT_TIME,
|
|
|
|
received_segment->stop);
|
2014-10-21 08:35:48 +00:00
|
|
|
running_duration = running_stop - running_start;
|
2014-07-03 12:34:11 +00:00
|
|
|
fail_if (received_segment->rate != segment->rate);
|
|
|
|
fail_if (received_segment->format != segment->format);
|
|
|
|
fail_unless_equals_int64 (received_segment->time, segment->position);
|
|
|
|
fail_unless_equals_int64 (received_segment->base, collect->expected_base);
|
|
|
|
fail_unless_equals_uint64 (received_segment->stop - received_segment->start,
|
2014-10-21 08:35:48 +00:00
|
|
|
segment->stop - segment->start);
|
|
|
|
|
|
|
|
collect->expected_base += running_duration;
|
|
|
|
|
|
|
|
GST_DEBUG ("Segment was valid, discarding expected Segment");
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstPadProbeReturn
|
|
|
|
sinkpad_event_probe (GstPad * sinkpad, GstEvent * event,
|
|
|
|
CollectStructure * collect)
|
|
|
|
{
|
|
|
|
Segment *segment;
|
|
|
|
|
|
|
|
GST_DEBUG_OBJECT (sinkpad, "event:%p (%s seqnum:%d) , collect:%p", event,
|
|
|
|
GST_EVENT_TYPE_NAME (event), GST_EVENT_SEQNUM (event), collect);
|
|
|
|
|
|
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
|
|
|
|
fail_if (collect->expected_segments == NULL,
|
|
|
|
"Received unexpected segment on pad: %s:%s",
|
|
|
|
GST_DEBUG_PAD_NAME (sinkpad));
|
|
|
|
|
|
|
|
if (!collect->gotsegment)
|
|
|
|
collect->seen_segments =
|
|
|
|
g_list_append (NULL, GINT_TO_POINTER (GST_EVENT_SEQNUM (event)));
|
|
|
|
else {
|
|
|
|
fail_if (g_list_find (collect->seen_segments,
|
|
|
|
GINT_TO_POINTER (GST_EVENT_SEQNUM (event))),
|
|
|
|
"Got a segment event we already saw before !");
|
|
|
|
collect->seen_segments =
|
|
|
|
g_list_append (collect->seen_segments,
|
|
|
|
GINT_TO_POINTER (GST_EVENT_SEQNUM (event)));
|
|
|
|
}
|
|
|
|
|
|
|
|
segment = (Segment *) collect->expected_segments->data;
|
|
|
|
|
|
|
|
if (compare_segments (collect, segment, event) &&
|
|
|
|
collect->keep_expected_segments == FALSE) {
|
|
|
|
collect->expected_segments =
|
|
|
|
g_list_remove (collect->expected_segments, segment);
|
|
|
|
g_free (segment);
|
|
|
|
}
|
|
|
|
|
|
|
|
collect->gotsegment = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstPadProbeReturn
|
|
|
|
sinkpad_buffer_probe (GstPad * sinkpad, GstBuffer * buffer,
|
|
|
|
CollectStructure * collect)
|
|
|
|
{
|
2014-07-15 12:59:54 +00:00
|
|
|
GST_LOG_OBJECT (sinkpad, "buffer:%p (%" GST_TIME_FORMAT ") , collect:%p",
|
2014-10-21 08:35:48 +00:00
|
|
|
buffer, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), collect);
|
|
|
|
fail_if (!collect->gotsegment,
|
|
|
|
"Received a buffer without a preceding segment");
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
GstPadProbeReturn
|
|
|
|
sinkpad_probe (GstPad * sinkpad, GstPadProbeInfo * info,
|
|
|
|
CollectStructure * collect)
|
|
|
|
{
|
|
|
|
if (info->type & GST_PAD_PROBE_TYPE_BUFFER)
|
|
|
|
return sinkpad_buffer_probe (sinkpad, (GstBuffer *) info->data, collect);
|
|
|
|
if (info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)
|
|
|
|
return sinkpad_event_probe (sinkpad, (GstEvent *) info->data, collect);
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstElement *
|
2014-08-15 13:48:14 +00:00
|
|
|
new_nle_src (const gchar * name, guint64 start, gint64 duration, gint priority)
|
2014-10-21 08:35:48 +00:00
|
|
|
{
|
2014-08-15 13:48:14 +00:00
|
|
|
GstElement *nlesource = NULL;
|
2014-10-21 08:35:48 +00:00
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
nlesource = gst_element_factory_make_or_warn ("nlesource", name);
|
|
|
|
fail_if (nlesource == NULL);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
g_object_set (G_OBJECT (nlesource),
|
2014-10-21 08:35:48 +00:00
|
|
|
"start", start,
|
|
|
|
"duration", duration, "inpoint", start, "priority", priority, NULL);
|
2015-06-25 09:03:12 +00:00
|
|
|
nle_object_commit (nlesource, FALSE);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
return nlesource;
|
2014-10-21 08:35:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GstElement *
|
2014-08-15 13:48:14 +00:00
|
|
|
videotest_nle_src (const gchar * name, guint64 start, gint64 duration,
|
2014-10-21 08:35:48 +00:00
|
|
|
gint pattern, guint priority)
|
|
|
|
{
|
2014-08-15 13:48:14 +00:00
|
|
|
GstElement *nlesource = NULL;
|
2014-10-21 08:35:48 +00:00
|
|
|
GstElement *videotestsrc = NULL;
|
|
|
|
GstCaps *caps =
|
|
|
|
gst_caps_from_string
|
|
|
|
("video/x-raw,format=(string)I420,framerate=(fraction)3/2");
|
|
|
|
|
|
|
|
fail_if (caps == NULL);
|
|
|
|
|
|
|
|
videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL);
|
|
|
|
g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL);
|
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
nlesource = new_nle_src (name, start, duration, priority);
|
|
|
|
g_object_set (G_OBJECT (nlesource), "caps", caps, NULL);
|
2014-10-21 08:35:48 +00:00
|
|
|
gst_caps_unref (caps);
|
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
gst_bin_add (GST_BIN (nlesource), videotestsrc);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
return nlesource;
|
2014-10-21 08:35:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GstElement *
|
2014-08-15 13:48:14 +00:00
|
|
|
videotest_nle_src_full (const gchar * name, guint64 start, gint64 duration,
|
2014-10-21 08:35:48 +00:00
|
|
|
guint64 inpoint, gint pattern, guint priority)
|
|
|
|
{
|
2014-08-15 13:48:14 +00:00
|
|
|
GstElement *nles;
|
2014-10-21 08:35:48 +00:00
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
nles = videotest_nle_src (name, start, duration, pattern, priority);
|
|
|
|
if (nles) {
|
|
|
|
g_object_set (G_OBJECT (nles), "inpoint", inpoint, NULL);
|
2014-10-21 08:35:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
return nles;
|
2014-10-21 08:35:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GstElement *
|
2014-08-15 13:48:14 +00:00
|
|
|
videotest_in_bin_nle_src (const gchar * name, guint64 start, gint64 duration,
|
2014-10-21 08:35:48 +00:00
|
|
|
gint pattern, guint priority)
|
|
|
|
{
|
2014-08-15 13:48:14 +00:00
|
|
|
GstElement *nlesource = NULL;
|
2014-10-21 08:35:48 +00:00
|
|
|
GstElement *videotestsrc = NULL;
|
|
|
|
GstElement *bin = NULL;
|
|
|
|
GstElement *alpha = NULL;
|
|
|
|
GstPad *srcpad = NULL;
|
|
|
|
|
|
|
|
alpha = gst_element_factory_make ("alpha", NULL);
|
|
|
|
if (alpha == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL);
|
|
|
|
g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL);
|
|
|
|
bin = gst_bin_new (NULL);
|
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
nlesource = new_nle_src (name, start, duration, priority);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
|
|
|
gst_bin_add (GST_BIN (bin), videotestsrc);
|
|
|
|
gst_bin_add (GST_BIN (bin), alpha);
|
|
|
|
|
|
|
|
gst_element_link_pads_full (videotestsrc, "src", alpha, "sink",
|
|
|
|
GST_PAD_LINK_CHECK_NOTHING);
|
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
gst_bin_add (GST_BIN (nlesource), bin);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
|
|
|
srcpad = gst_element_get_static_pad (alpha, "src");
|
|
|
|
|
|
|
|
gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad));
|
|
|
|
|
|
|
|
gst_object_unref (srcpad);
|
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
return nlesource;
|
2014-10-21 08:35:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GstElement *
|
|
|
|
audiotest_bin_src (const gchar * name, guint64 start,
|
|
|
|
gint64 duration, guint priority, gboolean intaudio)
|
|
|
|
{
|
|
|
|
GstElement *source = NULL;
|
|
|
|
GstElement *identity = NULL;
|
|
|
|
GstElement *audiotestsrc = NULL;
|
|
|
|
GstElement *audioconvert = NULL;
|
|
|
|
GstElement *bin = NULL;
|
|
|
|
GstCaps *caps;
|
|
|
|
GstPad *srcpad = NULL;
|
|
|
|
|
|
|
|
audiotestsrc = gst_element_factory_make_or_warn ("audiotestsrc", NULL);
|
|
|
|
identity = gst_element_factory_make_or_warn ("identity", NULL);
|
|
|
|
bin = gst_bin_new (NULL);
|
2014-08-15 13:48:14 +00:00
|
|
|
source = new_nle_src (name, start, duration, priority);
|
2014-10-21 08:35:48 +00:00
|
|
|
audioconvert = gst_element_factory_make_or_warn ("audioconvert", NULL);
|
|
|
|
|
|
|
|
if (intaudio)
|
|
|
|
caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE");
|
|
|
|
else
|
|
|
|
caps = gst_caps_from_string ("audio/x-raw,format=(string)F32LE");
|
|
|
|
|
|
|
|
gst_bin_add_many (GST_BIN (bin), audiotestsrc, audioconvert, identity, NULL);
|
|
|
|
gst_element_link_pads_full (audiotestsrc, "src", audioconvert, "sink",
|
|
|
|
GST_PAD_LINK_CHECK_NOTHING);
|
|
|
|
fail_if ((gst_element_link_filtered (audioconvert, identity, caps)) != TRUE);
|
|
|
|
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
|
|
|
|
gst_bin_add (GST_BIN (source), bin);
|
|
|
|
|
|
|
|
srcpad = gst_element_get_static_pad (identity, "src");
|
|
|
|
|
|
|
|
gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad));
|
|
|
|
|
|
|
|
gst_object_unref (srcpad);
|
|
|
|
|
|
|
|
return source;
|
|
|
|
}
|
|
|
|
|
|
|
|
GstElement *
|
|
|
|
new_operation (const gchar * name, const gchar * factory, guint64 start,
|
|
|
|
gint64 duration, guint priority)
|
|
|
|
{
|
2014-08-15 13:48:14 +00:00
|
|
|
GstElement *nleoperation = NULL;
|
2014-10-21 08:35:48 +00:00
|
|
|
GstElement *operation = NULL;
|
|
|
|
|
|
|
|
operation = gst_element_factory_make_or_warn (factory, NULL);
|
2014-08-15 13:48:14 +00:00
|
|
|
nleoperation = gst_element_factory_make_or_warn ("nleoperation", name);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
g_object_set (G_OBJECT (nleoperation),
|
2014-10-21 08:35:48 +00:00
|
|
|
"start", start, "duration", duration, "priority", priority, NULL);
|
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
gst_bin_add (GST_BIN (nleoperation), operation);
|
2014-10-21 08:35:48 +00:00
|
|
|
|
2014-08-15 13:48:14 +00:00
|
|
|
return nleoperation;
|
2014-10-21 08:35:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Segment *
|
|
|
|
segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop,
|
|
|
|
gint64 position)
|
|
|
|
{
|
|
|
|
Segment *segment;
|
|
|
|
|
|
|
|
segment = g_new0 (Segment, 1);
|
|
|
|
|
|
|
|
segment->rate = rate;
|
|
|
|
segment->format = format;
|
|
|
|
segment->start = start;
|
|
|
|
segment->stop = stop;
|
|
|
|
segment->position = position;
|
|
|
|
|
|
|
|
return segment;
|
|
|
|
}
|
|
|
|
|
|
|
|
GList *
|
|
|
|
copy_segment_list (GList * list)
|
|
|
|
{
|
|
|
|
GList *res = NULL;
|
|
|
|
|
|
|
|
while (list) {
|
|
|
|
Segment *pdata = (Segment *) list->data;
|
|
|
|
|
|
|
|
res =
|
|
|
|
g_list_append (res, segment_new (pdata->rate, pdata->format,
|
|
|
|
pdata->start, pdata->stop, pdata->position));
|
|
|
|
|
|
|
|
list = list->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
2014-06-27 14:12:12 +00:00
|
|
|
|
|
|
|
static GMutex lock;
|
|
|
|
static GCond cond;
|
|
|
|
static void
|
|
|
|
commited_cb (GstElement * comp, gboolean changed)
|
|
|
|
{
|
|
|
|
g_mutex_lock (&lock);
|
|
|
|
g_cond_signal (&cond);
|
|
|
|
g_mutex_unlock (&lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
commit_and_wait (GstElement * comp, gboolean * ret)
|
|
|
|
{
|
2014-06-30 14:21:30 +00:00
|
|
|
gulong handler_id =
|
|
|
|
g_signal_connect (comp, "commited", (GCallback) commited_cb, NULL);
|
2014-06-27 14:12:12 +00:00
|
|
|
g_mutex_lock (&lock);
|
2015-06-25 09:03:12 +00:00
|
|
|
*ret = nle_object_commit (comp, TRUE);
|
2014-06-27 14:12:12 +00:00
|
|
|
g_cond_wait (&cond, &lock);
|
|
|
|
g_mutex_unlock (&lock);
|
2014-06-28 12:44:24 +00:00
|
|
|
g_signal_handler_disconnect (comp, handler_id);
|
2014-06-27 14:12:12 +00:00
|
|
|
}
|
2014-06-30 14:21:30 +00:00
|
|
|
|
|
|
|
gboolean
|
2014-08-15 13:48:14 +00:00
|
|
|
nle_composition_remove (GstBin * comp, GstElement * object)
|
2014-06-30 14:21:30 +00:00
|
|
|
{
|
|
|
|
gboolean ret;
|
|
|
|
|
2014-07-19 09:41:56 +00:00
|
|
|
ret = gst_bin_remove (comp, object);
|
2014-06-30 14:21:30 +00:00
|
|
|
if (!ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
commit_and_wait ((GstElement *) comp, &ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean
|
2014-08-15 13:48:14 +00:00
|
|
|
nle_composition_add (GstBin * comp, GstElement * object)
|
2014-06-30 14:21:30 +00:00
|
|
|
{
|
2014-07-19 09:41:56 +00:00
|
|
|
return gst_bin_add (comp, object);
|
2014-06-30 14:21:30 +00:00
|
|
|
}
|
2019-01-14 02:34:20 +00:00
|
|
|
|
|
|
|
void
|
|
|
|
collect_free (CollectStructure * collect)
|
|
|
|
{
|
|
|
|
if (collect->seen_segments)
|
|
|
|
g_list_free (collect->seen_segments);
|
|
|
|
if (collect->expected_segments)
|
|
|
|
g_list_free_full (collect->expected_segments, (GDestroyNotify) g_free);
|
|
|
|
|
|
|
|
g_free (collect);
|
|
|
|
}
|