mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-04-10 18:14:15 +00:00
gst-launch: add index support
When option "-i" is given, set an index object on the pipeline and compute statistics for all index writers. Print a sumary when shutting down the pipeline.
This commit is contained in:
parent
3cb1180ff4
commit
88cda98939
2 changed files with 189 additions and 7 deletions
|
@ -4,7 +4,7 @@ gst\-launch \- build and run a GStreamer pipeline
|
|||
.SH "SYNOPSIS"
|
||||
\fBgst\-launch\fR \fI[OPTION...]\fR PIPELINE\-DESCRIPTION
|
||||
.SH "DESCRIPTION"
|
||||
.LP
|
||||
.LP
|
||||
\fIgst\-launch\fP is a tool that builds and runs basic
|
||||
\fIGStreamer\fP pipelines.
|
||||
|
||||
|
@ -44,6 +44,10 @@ Force an EOS event on sources before shutting the pipeline down. This is
|
|||
useful to make sure muxers create readable files when a muxing pipeline is
|
||||
shut down forcefully via Control-C.
|
||||
.TP 8
|
||||
.B \-i, \-\-index
|
||||
Gather and print index statistics. This is mostly useful for playback or
|
||||
recording pipelines.
|
||||
.TP 8
|
||||
.B \-o FILE, \-\-output=FILE
|
||||
Save XML representation of pipeline to FILE and exit (DEPRECATED, DO NOT USE)
|
||||
.TP 8
|
||||
|
@ -114,7 +118,7 @@ plugins to preload is to use the environment variable GST_PLUGIN_PATH
|
|||
|
||||
.SH "PIPELINE DESCRIPTION"
|
||||
|
||||
A pipeline consists \fIelements\fR and \fIlinks\fR. \fIElements\fR can be put
|
||||
A pipeline consists \fIelements\fR and \fIlinks\fR. \fIElements\fR can be put
|
||||
into \fIbins\fR of different sorts. \fIElements\fR, \fIlinks\fR and \fIbins\fR
|
||||
can be specified in a pipeline description in any order.
|
||||
|
||||
|
@ -138,7 +142,7 @@ Enumeration properties can be set by name, nick or value.
|
|||
\fI[BINTYPE.]\fR ( \fI[PROPERTY1 ...]\fR PIPELINE-DESCRIPTION )
|
||||
.br
|
||||
|
||||
Specifies that a bin of type BINTYPE is created and the given properties are
|
||||
Specifies that a bin of type BINTYPE is created and the given properties are
|
||||
set. Every element between the braces is put into the bin. Please note the dot
|
||||
that has to be used after the BINTYPE. You will almost never need this
|
||||
functionality, it is only really useful for applications using the
|
||||
|
@ -194,11 +198,11 @@ and the type can have the following case-insensitive values:
|
|||
.br
|
||||
- \fBl\fR or \fBlist\fR for lists
|
||||
.br
|
||||
If no type was given, the following order is tried: integer, float, boolean,
|
||||
If no type was given, the following order is tried: integer, float, boolean,
|
||||
string.
|
||||
.br
|
||||
Integer values must be parsable by \fBstrtol()\fP, floats by \fBstrtod()\fP. FOURCC values may
|
||||
either be integers or strings. Boolean values are (case insensitive) \fIyes\fR,
|
||||
either be integers or strings. Boolean values are (case insensitive) \fIyes\fR,
|
||||
\fIno\fR, \fItrue\fR or \fIfalse\fR and may like strings be escaped with " or '.
|
||||
.br
|
||||
Ranges are in this format: [ VALUE, VALUE ]
|
||||
|
@ -390,7 +394,7 @@ automatically. To make this even easier, you can use the playbin element:
|
|||
.B
|
||||
gst\-launch playbin uri=file:///home/joe/foo.avi
|
||||
.br
|
||||
|
||||
|
||||
|
||||
.B Filtered connections
|
||||
|
||||
|
@ -438,7 +442,7 @@ Specifies a list of directories to scan for additional plugins.
|
|||
These take precedence over the system plugins.
|
||||
.TP
|
||||
\fBGST_PLUGIN_SYSTEM_PATH\fR
|
||||
Specifies a list of plugins that are always loaded by default. If not set,
|
||||
Specifies a list of plugins that are always loaded by default. If not set,
|
||||
this defaults to the system-installed path, and the plugins installed in the
|
||||
user's home directory
|
||||
.TP
|
||||
|
|
|
@ -250,6 +250,159 @@ fault_setup (void)
|
|||
}
|
||||
#endif /* DISABLE_FAULT_HANDLER */
|
||||
|
||||
typedef struct _GstIndexStats
|
||||
{
|
||||
gint id;
|
||||
gchar *desc;
|
||||
|
||||
guint num_frames;
|
||||
guint num_keyframes;
|
||||
guint num_dltframes;
|
||||
GstClockTime last_keyframe;
|
||||
GstClockTime last_dltframe;
|
||||
GstClockTime min_keyframe_gap;
|
||||
GstClockTime max_keyframe_gap;
|
||||
GstClockTime avg_keyframe_gap;
|
||||
} GstIndexStats;
|
||||
|
||||
static void
|
||||
entry_added (GstIndex * index, GstIndexEntry * entry, gpointer user_data)
|
||||
{
|
||||
GPtrArray *index_stats = (GPtrArray *) user_data;
|
||||
GstIndexStats *s;
|
||||
|
||||
switch (entry->type) {
|
||||
case GST_INDEX_ENTRY_ID:
|
||||
/* we have a new writer */
|
||||
GST_DEBUG_OBJECT (index, "id %d: describes writer %s", entry->id,
|
||||
GST_INDEX_ID_DESCRIPTION (entry));
|
||||
if (entry->id >= index_stats->len) {
|
||||
g_ptr_array_set_size (index_stats, entry->id + 1);
|
||||
}
|
||||
s = g_new (GstIndexStats, 1);
|
||||
s->id = entry->id;
|
||||
s->desc = g_strdup (GST_INDEX_ID_DESCRIPTION (entry));
|
||||
s->num_frames = s->num_keyframes = s->num_dltframes = 0;
|
||||
s->last_keyframe = s->last_dltframe = GST_CLOCK_TIME_NONE;
|
||||
s->min_keyframe_gap = s->max_keyframe_gap = s->avg_keyframe_gap =
|
||||
GST_CLOCK_TIME_NONE;
|
||||
g_ptr_array_index (index_stats, entry->id) = s;
|
||||
break;
|
||||
case GST_INDEX_ENTRY_FORMAT:
|
||||
/* have not found any code calling this */
|
||||
GST_DEBUG_OBJECT (index, "id %d: registered format %d for %s\n",
|
||||
entry->id, GST_INDEX_FORMAT_FORMAT (entry),
|
||||
GST_INDEX_FORMAT_KEY (entry));
|
||||
break;
|
||||
case GST_INDEX_ENTRY_ASSOCIATION:
|
||||
{
|
||||
gint64 ts;
|
||||
GstAssocFlags flags = GST_INDEX_ASSOC_FLAGS (entry);
|
||||
|
||||
s = g_ptr_array_index (index_stats, entry->id);
|
||||
gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &ts);
|
||||
|
||||
if (flags & GST_ASSOCIATION_FLAG_KEY_UNIT) {
|
||||
s->num_keyframes++;
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (ts)) {
|
||||
if (GST_CLOCK_TIME_IS_VALID (s->last_keyframe)) {
|
||||
GstClockTimeDiff d = GST_CLOCK_DIFF (s->last_keyframe, ts);
|
||||
|
||||
if (G_UNLIKELY (d < 0)) {
|
||||
GST_WARNING ("received out-of-order keyframe at %"
|
||||
GST_TIME_FORMAT, GST_TIME_ARGS (ts));
|
||||
/* FIXME: does it still make sense to use that for the statistics */
|
||||
d = GST_CLOCK_DIFF (ts, s->last_keyframe);
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (s->min_keyframe_gap)) {
|
||||
if (d < s->min_keyframe_gap)
|
||||
s->min_keyframe_gap = d;
|
||||
} else {
|
||||
s->min_keyframe_gap = d;
|
||||
}
|
||||
if (GST_CLOCK_TIME_IS_VALID (s->max_keyframe_gap)) {
|
||||
if (d > s->max_keyframe_gap)
|
||||
s->max_keyframe_gap = d;
|
||||
} else {
|
||||
s->max_keyframe_gap = d;
|
||||
}
|
||||
if (GST_CLOCK_TIME_IS_VALID (s->avg_keyframe_gap)) {
|
||||
s->avg_keyframe_gap = (d + s->num_frames * s->avg_keyframe_gap) /
|
||||
(s->num_frames + 1);
|
||||
} else {
|
||||
s->avg_keyframe_gap = d;
|
||||
}
|
||||
}
|
||||
s->last_keyframe = ts;
|
||||
}
|
||||
}
|
||||
if (flags & GST_ASSOCIATION_FLAG_DELTA_UNIT) {
|
||||
s->num_dltframes++;
|
||||
if (GST_CLOCK_TIME_IS_VALID (ts)) {
|
||||
s->last_dltframe = ts;
|
||||
}
|
||||
}
|
||||
s->num_frames++;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* print statistics from the entry_added callback, free the entries */
|
||||
static void
|
||||
print_index_stats (GPtrArray * index_stats)
|
||||
{
|
||||
gint i;
|
||||
|
||||
if (index_stats->len) {
|
||||
g_print (_("Index statistics\n"));
|
||||
}
|
||||
|
||||
for (i = 0; i < index_stats->len; i++) {
|
||||
GstIndexStats *s = g_ptr_array_index (index_stats, i);
|
||||
if (s) {
|
||||
g_print ("id %d, %s\n", s->id, s->desc);
|
||||
if (s->num_frames) {
|
||||
GstClockTime last_frame = s->last_keyframe;
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (s->last_dltframe)) {
|
||||
if (!GST_CLOCK_TIME_IS_VALID (last_frame) ||
|
||||
(s->last_dltframe > last_frame))
|
||||
last_frame = s->last_dltframe;
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (last_frame)) {
|
||||
g_print (" total time = %" GST_TIME_FORMAT "\n",
|
||||
GST_TIME_ARGS (last_frame));
|
||||
}
|
||||
g_print (" frame/keyframe rate = %u / %u = ", s->num_frames,
|
||||
s->num_keyframes);
|
||||
if (s->num_keyframes)
|
||||
g_print ("%lf\n", s->num_frames / (gdouble) s->num_keyframes);
|
||||
else
|
||||
g_print ("-\n");
|
||||
if (s->num_keyframes) {
|
||||
g_print (" min/avg/max keyframe gap = %" GST_TIME_FORMAT ", %"
|
||||
GST_TIME_FORMAT ", %" GST_TIME_FORMAT "\n",
|
||||
GST_TIME_ARGS (s->min_keyframe_gap),
|
||||
GST_TIME_ARGS (s->avg_keyframe_gap),
|
||||
GST_TIME_ARGS (s->max_keyframe_gap));
|
||||
}
|
||||
} else {
|
||||
g_print (" no stats\n");
|
||||
}
|
||||
|
||||
g_free (s->desc);
|
||||
g_free (s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_error_message (GstMessage * msg)
|
||||
{
|
||||
|
@ -741,6 +894,7 @@ main (int argc, char *argv[])
|
|||
gboolean no_sigusr_handler = FALSE;
|
||||
gboolean trace = FALSE;
|
||||
gboolean eos_on_shutdown = FALSE;
|
||||
gboolean check_index = FALSE;
|
||||
gchar *savefile = NULL;
|
||||
gchar *exclude_args = NULL;
|
||||
#ifndef GST_DISABLE_OPTION_PARSING
|
||||
|
@ -767,12 +921,16 @@ main (int argc, char *argv[])
|
|||
N_("Print alloc trace (if enabled at compile time)"), NULL},
|
||||
{"eos-on-shutdown", 'e', 0, G_OPTION_ARG_NONE, &eos_on_shutdown,
|
||||
N_("Force EOS on sources before shutting the pipeline down"), NULL},
|
||||
{"index", 'i', 0, G_OPTION_ARG_NONE, &check_index,
|
||||
N_("Gather and print index statistics"), NULL},
|
||||
GST_TOOLS_GOPTION_VERSION,
|
||||
{NULL}
|
||||
};
|
||||
GOptionContext *ctx;
|
||||
GError *err = NULL;
|
||||
#endif
|
||||
GstIndex *index;
|
||||
GPtrArray *index_stats = NULL;
|
||||
gchar **argvn;
|
||||
GError *error = NULL;
|
||||
gint res = 0;
|
||||
|
@ -893,6 +1051,21 @@ main (int argc, char *argv[])
|
|||
pipeline = real_pipeline;
|
||||
}
|
||||
|
||||
if (check_index) {
|
||||
/* gst_index_new() creates a null-index, it does not store anything, but
|
||||
* the entry-added signal works and this is what we use to build the
|
||||
* statistics */
|
||||
index = gst_index_new ();
|
||||
if (index) {
|
||||
index_stats = g_ptr_array_new ();
|
||||
g_signal_connect (G_OBJECT (index), "entry-added",
|
||||
G_CALLBACK (entry_added), index_stats);
|
||||
g_object_set (G_OBJECT (index), "resolver", GST_INDEX_RESOLVER_GTYPE,
|
||||
NULL);
|
||||
gst_element_set_index (pipeline, index);
|
||||
}
|
||||
}
|
||||
|
||||
bus = gst_element_get_bus (pipeline);
|
||||
gst_bus_set_sync_handler (bus, bus_sync_handler, (gpointer) pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
@ -985,6 +1158,11 @@ main (int argc, char *argv[])
|
|||
gst_element_set_state (pipeline, GST_STATE_READY);
|
||||
gst_element_get_state (pipeline, &state, &pending, GST_CLOCK_TIME_NONE);
|
||||
|
||||
if (check_index) {
|
||||
print_index_stats (index_stats);
|
||||
g_ptr_array_free (index_stats, TRUE);
|
||||
}
|
||||
|
||||
end:
|
||||
PRINT (_("Setting pipeline to NULL ...\n"));
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
|
|
Loading…
Reference in a new issue