/* * Copyright 2009 Nokia Corporation * 2006 Zeeshan Ali . * * This program 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 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * SECTION:element-fpsdisplaysink * * Can display the current and average framerate as a testoverlay or on stdout. * * * Example launch lines * |[ * gst-launch-1.0 videotestsrc ! fpsdisplaysink * gst-launch-1.0 videotestsrc ! fpsdisplaysink text-overlay=false * gst-launch-1.0 filesrc location=video.avi ! decodebin name=d ! queue ! fpsdisplaysink d. ! queue ! fakesink sync=true * gst-launch-1.0 playbin uri=file:///path/to/video.avi video-sink="fpsdisplaysink" audio-sink=fakesink * ]| * */ /* FIXME: * - can we avoid plugging the textoverlay? * - gst-seek 15 "videotestsrc ! fpsdisplaysink" dies when closing gst-seek * * NOTE: * - if we make ourself RANK_PRIMARY+10 or something that autovideosink would * select and fpsdisplaysink is set to use autovideosink as its internal sink * it doesn't work. Reason: autovideosink creates a fpsdisplaysink, that * creates an autovideosink, that... */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "fpsdisplaysink.h" #define DEFAULT_SIGNAL_FPS_MEASUREMENTS FALSE #define DEFAULT_FPS_UPDATE_INTERVAL_MS 500 /* 500 ms */ #define DEFAULT_FONT "Sans 15" #define DEFAULT_SILENT FALSE #define DEFAULT_LAST_MESSAGE NULL /* generic templates */ static GstStaticPadTemplate fps_display_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); GST_DEBUG_CATEGORY_STATIC (fps_display_sink_debug); #define GST_CAT_DEFAULT fps_display_sink_debug #define DEFAULT_SYNC TRUE enum { /* FILL ME */ SIGNAL_FPS_MEASUREMENTS, LAST_SIGNAL }; enum { PROP_0, PROP_SYNC, PROP_TEXT_OVERLAY, PROP_VIDEO_SINK, PROP_FPS_UPDATE_INTERVAL, PROP_MAX_FPS, PROP_MIN_FPS, PROP_SIGNAL_FPS_MEASUREMENTS, PROP_FRAMES_DROPPED, PROP_FRAMES_RENDERED, PROP_SILENT, PROP_LAST_MESSAGE /* FILL ME */ }; static GstBinClass *parent_class = NULL; static GstStateChangeReturn fps_display_sink_change_state (GstElement * element, GstStateChange transition); static void fps_display_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void fps_display_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void fps_display_sink_dispose (GObject * object); static void fps_display_sink_handle_message (GstBin * bin, GstMessage * message); static gboolean display_current_fps (gpointer data); static guint fpsdisplaysink_signals[LAST_SIGNAL] = { 0 }; static GParamSpec *pspec_last_message = NULL; static void fps_display_sink_class_init (GstFPSDisplaySinkClass * klass) { GObjectClass *gobject_klass = G_OBJECT_CLASS (klass); GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (klass); GstBinClass *bin_class = GST_BIN_CLASS (klass); parent_class = g_type_class_peek_parent (klass); gobject_klass->set_property = fps_display_sink_set_property; gobject_klass->get_property = fps_display_sink_get_property; gobject_klass->dispose = fps_display_sink_dispose; bin_class->handle_message = fps_display_sink_handle_message; g_object_class_install_property (gobject_klass, PROP_SYNC, g_param_spec_boolean ("sync", "Sync", "Sync on the clock (if the internally used sink doesn't " "have this property it will be ignored", DEFAULT_SYNC, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_TEXT_OVERLAY, g_param_spec_boolean ("text-overlay", "text-overlay", "Whether to use text-overlay", TRUE, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK, g_param_spec_object ("video-sink", "video-sink", "Video sink to use (Must only be called on NULL state)", GST_TYPE_ELEMENT, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_FPS_UPDATE_INTERVAL, g_param_spec_int ("fps-update-interval", "Fps update interval", "Time between consecutive frames per second measures and update " " (in ms). Should be set on NULL state", 1, G_MAXINT, DEFAULT_FPS_UPDATE_INTERVAL_MS, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_MAX_FPS, g_param_spec_double ("max-fps", "Max fps", "Maximum fps rate measured. Reset when going from NULL to READY." "-1 means no measurement has yet been done", -1, G_MAXDOUBLE, -1, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE)); g_object_class_install_property (gobject_klass, PROP_MIN_FPS, g_param_spec_double ("min-fps", "Min fps", "Minimum fps rate measured. Reset when going from NULL to READY." "-1 means no measurement has yet been done", -1, G_MAXDOUBLE, -1, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE)); g_object_class_install_property (gobject_klass, PROP_FRAMES_DROPPED, g_param_spec_uint ("frames-dropped", "dropped frames", "Number of frames dropped by the sink", 0, G_MAXUINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE)); g_object_class_install_property (gobject_klass, PROP_FRAMES_RENDERED, g_param_spec_uint ("frames-rendered", "rendered frames", "Number of frames rendered", 0, G_MAXUINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE)); g_object_class_install_property (gobject_klass, PROP_SILENT, g_param_spec_boolean ("silent", "enable stdout output", "Don't produce last_message events", DEFAULT_SILENT, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_SIGNAL_FPS_MEASUREMENTS, g_param_spec_boolean ("signal-fps-measurements", "Signal fps measurements", "If the fps-measurements signal should be emited.", DEFAULT_SIGNAL_FPS_MEASUREMENTS, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); pspec_last_message = g_param_spec_string ("last-message", "Last Message", "The message describing current status", DEFAULT_LAST_MESSAGE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_klass, PROP_LAST_MESSAGE, pspec_last_message); /** * GstFPSDisplaySink::fps-measurements: * @fpsdisplaysink: a #GstFPSDisplaySink * @fps: The current measured fps * @droprate: The rate at which buffers are being dropped * @avgfps: The average fps * * Signals the application about the measured fps */ fpsdisplaysink_signals[SIGNAL_FPS_MEASUREMENTS] = g_signal_new ("fps-measurements", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE); gstelement_klass->change_state = fps_display_sink_change_state; gst_element_class_add_pad_template (gstelement_klass, gst_static_pad_template_get (&fps_display_sink_template)); gst_element_class_set_static_metadata (gstelement_klass, "Measure and show framerate on videosink", "Sink/Video", "Shows the current frame-rate and drop-rate of the videosink as overlay or text on stdout", "Zeeshan Ali , Stefan Kost "); } static GstPadProbeReturn on_video_sink_data_flow (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) { GstMiniObject *mini_obj = GST_PAD_PROBE_INFO_DATA (info); GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (user_data); if (GST_IS_BUFFER (mini_obj)) { GstClockTime ts; /* assume the frame is going to be rendered. If it isnt', we'll get a qos * message and reset ->frames_rendered from there. */ g_atomic_int_inc (&self->frames_rendered); ts = gst_util_get_timestamp (); if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (self->start_ts))) { self->interval_ts = self->last_ts = self->start_ts = ts; } if (GST_CLOCK_DIFF (self->interval_ts, ts) > self->fps_update_interval) { display_current_fps (self); self->interval_ts = ts; } } return GST_PAD_PROBE_OK; } static void update_sub_sync (GstElement * sink, gpointer data) { /* Some sinks (like autovideosink) don't have the sync property so * we check it exists before setting it to avoid a warning at * runtime. */ if (g_object_class_find_property (G_OBJECT_GET_CLASS (sink), "sync")) g_object_set (sink, "sync", *((gboolean *) data), NULL); else GST_WARNING ("Internal sink doesn't have sync property"); } static void update_sub_sync_foreach (const GValue * item, gpointer data) { GstElement *sink = g_value_get_object (item); update_sub_sync (sink, data); } static void fps_display_sink_update_sink_sync (GstFPSDisplaySink * self) { GstIterator *iterator; if (self->video_sink == NULL) return; if (GST_IS_BIN (self->video_sink)) { iterator = gst_bin_iterate_sinks (GST_BIN (self->video_sink)); gst_iterator_foreach (iterator, (GstIteratorForeachFunction) update_sub_sync_foreach, (void *) &self->sync); gst_iterator_free (iterator); } else update_sub_sync (self->video_sink, (void *) &self->sync); } static void update_video_sink (GstFPSDisplaySink * self, GstElement * video_sink) { GstPad *sink_pad; if (self->video_sink) { /* remove pad probe */ sink_pad = gst_element_get_static_pad (self->video_sink, "sink"); gst_pad_remove_probe (sink_pad, self->data_probe_id); gst_object_unref (sink_pad); self->data_probe_id = -1; /* remove ghost pad target */ gst_ghost_pad_set_target (GST_GHOST_PAD (self->ghost_pad), NULL); /* remove old sink */ gst_bin_remove (GST_BIN (self), self->video_sink); gst_object_unref (self->video_sink); } /* create child elements */ self->video_sink = video_sink; if (self->video_sink == NULL) return; fps_display_sink_update_sink_sync (self); /* take a ref before bin takes the ownership */ gst_object_ref (self->video_sink); gst_bin_add (GST_BIN (self), self->video_sink); /* attach or pad probe */ sink_pad = gst_element_get_static_pad (self->video_sink, "sink"); self->data_probe_id = gst_pad_add_probe (sink_pad, GST_PAD_PROBE_TYPE_DATA_BOTH, on_video_sink_data_flow, (gpointer) self, NULL); gst_object_unref (sink_pad); } static void fps_display_sink_init (GstFPSDisplaySink * self, GstFPSDisplaySinkClass * g_class) { self->sync = DEFAULT_SYNC; self->signal_measurements = DEFAULT_SIGNAL_FPS_MEASUREMENTS; self->use_text_overlay = TRUE; self->fps_update_interval = GST_MSECOND * DEFAULT_FPS_UPDATE_INTERVAL_MS; self->video_sink = NULL; self->max_fps = -1; self->min_fps = -1; self->silent = DEFAULT_SILENT; self->last_message = g_strdup (DEFAULT_LAST_MESSAGE); self->ghost_pad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK); gst_element_add_pad (GST_ELEMENT (self), self->ghost_pad); } static gboolean display_current_fps (gpointer data) { GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (data); guint64 frames_rendered, frames_dropped; gdouble rr, dr, average_fps; gchar fps_message[256]; gdouble time_diff, time_elapsed; GstClockTime current_ts = gst_util_get_timestamp (); frames_rendered = g_atomic_int_get (&self->frames_rendered); frames_dropped = g_atomic_int_get (&self->frames_dropped); if ((frames_rendered + frames_dropped) == 0) { /* in case timer fired and we didn't yet get any QOS events */ return TRUE; } time_diff = (gdouble) (current_ts - self->last_ts) / GST_SECOND; time_elapsed = (gdouble) (current_ts - self->start_ts) / GST_SECOND; rr = (gdouble) (frames_rendered - self->last_frames_rendered) / time_diff; dr = (gdouble) (frames_dropped - self->last_frames_dropped) / time_diff; average_fps = (gdouble) frames_rendered / time_elapsed; if (self->max_fps == -1 || rr > self->max_fps) { self->max_fps = rr; GST_DEBUG_OBJECT (self, "Updated max-fps to %f", rr); } if (self->min_fps == -1 || rr < self->min_fps) { self->min_fps = rr; GST_DEBUG_OBJECT (self, "Updated min-fps to %f", rr); } if (self->signal_measurements) { GST_LOG_OBJECT (self, "Signaling measurements: fps:%f droprate:%f " "avg-fps:%f", rr, dr, average_fps); g_signal_emit (G_OBJECT (self), fpsdisplaysink_signals[SIGNAL_FPS_MEASUREMENTS], 0, rr, dr, average_fps); } /* Display on a single line to make it easier to read and import * into, for example, excel.. note: it would be nice to show * timestamp too.. need to check if there is a sane way to log * timestamp of last rendered buffer, so we could correlate dips * in framerate to certain positions in the stream. */ if (dr == 0.0) { g_snprintf (fps_message, 255, "rendered: %" G_GUINT64_FORMAT ", dropped: %" G_GUINT64_FORMAT ", current: %.2f, average: %.2f", frames_rendered, frames_dropped, rr, average_fps); } else { g_snprintf (fps_message, 255, "rendered: %" G_GUINT64_FORMAT ", dropped: %" G_GUINT64_FORMAT ", fps: %.2f, drop rate: %.2f", frames_rendered, frames_dropped, rr, dr); } if (self->use_text_overlay) { g_object_set (self->text_overlay, "text", fps_message, NULL); } if (!self->silent) { GST_OBJECT_LOCK (self); g_free (self->last_message); self->last_message = g_strdup (fps_message); GST_OBJECT_UNLOCK (self); g_object_notify_by_pspec ((GObject *) self, pspec_last_message); } self->last_frames_rendered = frames_rendered; self->last_frames_dropped = frames_dropped; self->last_ts = current_ts; return TRUE; } static void fps_display_sink_start (GstFPSDisplaySink * self) { GstPad *target_pad = NULL; /* Init counters */ self->frames_rendered = 0; self->frames_dropped = 0; self->last_frames_rendered = G_GUINT64_CONSTANT (0); self->last_frames_dropped = G_GUINT64_CONSTANT (0); self->max_fps = -1; self->min_fps = -1; /* init time stamps */ self->last_ts = self->start_ts = self->interval_ts = GST_CLOCK_TIME_NONE; GST_DEBUG_OBJECT (self, "Use text-overlay? %d", self->use_text_overlay); if (self->use_text_overlay) { if (!self->text_overlay) { self->text_overlay = gst_element_factory_make ("textoverlay", "fps-display-text-overlay"); if (!self->text_overlay) { GST_WARNING_OBJECT (self, "text-overlay element could not be created"); self->use_text_overlay = FALSE; goto no_text_overlay; } gst_object_ref (self->text_overlay); g_object_set (self->text_overlay, "font-desc", DEFAULT_FONT, "silent", FALSE, NULL); gst_bin_add (GST_BIN (self), self->text_overlay); if (!gst_element_link (self->text_overlay, self->video_sink)) { GST_ERROR_OBJECT (self, "Could not link elements"); } } target_pad = gst_element_get_static_pad (self->text_overlay, "video_sink"); } no_text_overlay: if (!self->use_text_overlay) { if (self->text_overlay) { gst_element_unlink (self->text_overlay, self->video_sink); gst_bin_remove (GST_BIN (self), self->text_overlay); self->text_overlay = NULL; } target_pad = gst_element_get_static_pad (self->video_sink, "sink"); } gst_ghost_pad_set_target (GST_GHOST_PAD (self->ghost_pad), target_pad); gst_object_unref (target_pad); } static void fps_display_sink_stop (GstFPSDisplaySink * self) { if (self->text_overlay) { gst_element_unlink (self->text_overlay, self->video_sink); gst_bin_remove (GST_BIN (self), self->text_overlay); gst_object_unref (self->text_overlay); self->text_overlay = NULL; } if (!self->silent) { gchar *str; /* print the max and minimum fps values */ str = g_strdup_printf ("Max-fps: %0.2f, Min-fps: %0.2f", self->max_fps, self->min_fps); GST_OBJECT_LOCK (self); g_free (self->last_message); self->last_message = str; GST_OBJECT_UNLOCK (self); g_object_notify_by_pspec ((GObject *) self, pspec_last_message); } GST_OBJECT_LOCK (self); g_free (self->last_message); self->last_message = NULL; GST_OBJECT_UNLOCK (self); } static void fps_display_sink_dispose (GObject * object) { GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (object); if (self->video_sink) { gst_object_unref (self->video_sink); self->video_sink = NULL; } if (self->text_overlay) { gst_object_unref (self->text_overlay); self->text_overlay = NULL; } GST_OBJECT_LOCK (self); g_free (self->last_message); self->last_message = NULL; GST_OBJECT_UNLOCK (self); G_OBJECT_CLASS (parent_class)->dispose (object); } static void fps_display_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (object); switch (prop_id) { case PROP_SYNC: self->sync = g_value_get_boolean (value); fps_display_sink_update_sink_sync (self); break; case PROP_TEXT_OVERLAY: self->use_text_overlay = g_value_get_boolean (value); if (self->text_overlay) { if (!self->use_text_overlay) { GST_DEBUG_OBJECT (self, "text-overlay set to false"); g_object_set (self->text_overlay, "text", "", "silent", TRUE, NULL); } else { GST_DEBUG_OBJECT (self, "text-overlay set to true"); g_object_set (self->text_overlay, "silent", FALSE, NULL); } } break; case PROP_VIDEO_SINK: /* FIXME should we add a state-lock or a lock around here? * need to check if it is possible that a state change NULL->READY can * happen while this code is executing on a different thread */ if (GST_STATE (self) != GST_STATE_NULL) { g_warning ("Can't set video-sink property of fpsdisplaysink if not on " "NULL state"); break; } update_video_sink (self, (GstElement *) g_value_get_object (value)); break; case PROP_FPS_UPDATE_INTERVAL: self->fps_update_interval = GST_MSECOND * (GstClockTime) g_value_get_int (value); break; case PROP_SIGNAL_FPS_MEASUREMENTS: self->signal_measurements = g_value_get_boolean (value); break; case PROP_SILENT: self->silent = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fps_display_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (object); switch (prop_id) { case PROP_SYNC: g_value_set_boolean (value, self->sync); break; case PROP_TEXT_OVERLAY: g_value_set_boolean (value, self->use_text_overlay); break; case PROP_VIDEO_SINK: g_value_set_object (value, self->video_sink); break; case PROP_FPS_UPDATE_INTERVAL: g_value_set_int (value, (gint) (self->fps_update_interval / GST_MSECOND)); break; case PROP_MAX_FPS: g_value_set_double (value, self->max_fps); break; case PROP_MIN_FPS: g_value_set_double (value, self->min_fps); break; case PROP_FRAMES_DROPPED: g_value_set_uint (value, g_atomic_int_get (&self->frames_dropped)); break; case PROP_FRAMES_RENDERED: g_value_set_uint (value, g_atomic_int_get (&self->frames_rendered)); break; case PROP_SIGNAL_FPS_MEASUREMENTS: g_value_set_boolean (value, self->signal_measurements); break; case PROP_SILENT: g_value_set_boolean (value, self->silent); break; case PROP_LAST_MESSAGE: GST_OBJECT_LOCK (self); g_value_set_string (value, self->last_message); GST_OBJECT_UNLOCK (self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstStateChangeReturn fps_display_sink_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (self->video_sink == NULL) { GstElement *video_sink; GST_DEBUG_OBJECT (self, "No video sink set, creating autovideosink"); video_sink = gst_element_factory_make ("autovideosink", "fps-display-video_sink"); update_video_sink (self, video_sink); } if (self->video_sink != NULL) { fps_display_sink_start (self); } else { GST_ELEMENT_ERROR (self, LIBRARY, INIT, ("No video sink set and autovideosink is not available"), (NULL)); ret = GST_STATE_CHANGE_FAILURE; } break; case GST_STATE_CHANGE_READY_TO_PAUSED: case GST_STATE_CHANGE_PAUSED_TO_PLAYING: /* reinforce our sync to children, as they might have changed * internally */ fps_display_sink_update_sink_sync (self); break; default: break; } ret = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, change_state, (element, transition), GST_STATE_CHANGE_SUCCESS); switch (transition) { case GST_STATE_CHANGE_READY_TO_NULL: fps_display_sink_stop (self); break; default: break; } return ret; } static void fps_display_sink_handle_message (GstBin * bin, GstMessage * message) { GstFPSDisplaySink *self = (GstFPSDisplaySink *) bin; if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_QOS) { GstFormat format; guint64 rendered, dropped; gst_message_parse_qos_stats (message, &format, &rendered, &dropped); if (format != GST_FORMAT_UNDEFINED) { if (rendered != -1) g_atomic_int_set (&self->frames_rendered, rendered); if (dropped != -1) g_atomic_int_set (&self->frames_dropped, dropped); } } GST_BIN_CLASS (parent_class)->handle_message (bin, message); } GType fps_display_sink_get_type (void) { static GType fps_display_sink_type = 0; if (!fps_display_sink_type) { static const GTypeInfo fps_display_sink_info = { sizeof (GstFPSDisplaySinkClass), NULL, NULL, (GClassInitFunc) fps_display_sink_class_init, NULL, NULL, sizeof (GstFPSDisplaySink), 0, (GInstanceInitFunc) fps_display_sink_init, }; fps_display_sink_type = g_type_register_static (GST_TYPE_BIN, "GstFPSDisplaySink", &fps_display_sink_info, 0); GST_DEBUG_CATEGORY_INIT (fps_display_sink_debug, "fpsdisplaysink", 0, "FPS Display Sink"); } return fps_display_sink_type; }