blob: 489d88183ae0afefdd57efcdcabb6b85e526e9b5 [file] [log] [blame]
Stefan Koste0137362009-04-16 18:36:13 +03001/*
2 * Copyright 2009 Nokia Corporation <multimedia@maemo.org>
3 * 2006 Zeeshan Ali <zeeshan.ali@nokia.com>.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20/**
Tim-Philipp Müller76535b02009-05-01 14:25:40 +010021 * SECTION:element-fpsdisplaysink
Stefan Koste0137362009-04-16 18:36:13 +030022 *
Stefan Kost193f6382009-04-20 10:24:37 +030023 * Can display the current and average framerate as a testoverlay or on stdout.
24 *
25 * <refsect2>
26 * <title>Example launch lines</title>
Stefan Koste0137362009-04-16 18:36:13 +030027 * |[
28 * gst-launch videotestsrc ! fpsdisplaysink
29 * gst-launch videotestsrc ! fpsdisplaysink text-overlay=false
Stefan Kost790235e2009-04-20 09:49:32 +030030 * gst-launch filesrc location=video.avi ! decodebin2 name=d ! queue ! fpsdisplaysink d. ! queue ! fakesink sync=true
Stefan Kost9a03a432010-08-18 11:35:44 +030031 * gst-launch playbin2 uri=file:///path/to/video.avi video-sink="fpsdisplaysink" audio-sink=fakesink
Stefan Koste0137362009-04-16 18:36:13 +030032 * ]|
Stefan Kost193f6382009-04-20 10:24:37 +030033 * </refsect2>
Stefan Koste0137362009-04-16 18:36:13 +030034 */
35/* FIXME:
Stefan Kost193f6382009-04-20 10:24:37 +030036 * - can we avoid plugging the textoverlay?
Stefan Kost193f6382009-04-20 10:24:37 +030037 * - gst-seek 15 "videotestsrc ! fpsdisplaysink" dies when closing gst-seek
Thiago Santosa4f30d52009-12-17 07:54:04 -030038 *
39 * NOTE:
40 * - if we make ourself RANK_PRIMARY+10 or something that autovideosink would
41 * select and fpsdisplaysink is set to use autovideosink as its internal sink
42 * it doesn't work. Reason: autovideosink creates a fpsdisplaysink, that
43 * creates an autovideosink, that...
Stefan Koste0137362009-04-16 18:36:13 +030044 */
45
46#ifdef HAVE_CONFIG_H
47#include "config.h"
48#endif
49
Thiago Santos57b9ce72010-06-24 12:37:36 -030050#include "debugutils-marshal.h"
Stefan Koste0137362009-04-16 18:36:13 +030051#include "fpsdisplaysink.h"
52#include <gst/interfaces/xoverlay.h>
53
Thiago Santos57b9ce72010-06-24 12:37:36 -030054#define DEFAULT_SIGNAL_FPS_MEASUREMENTS FALSE
Thiago Santosdc8f1db2010-06-24 10:23:02 -030055#define DEFAULT_FPS_UPDATE_INTERVAL_MS 500 /* 500 ms */
Stefan Kost9a03a432010-08-18 11:35:44 +030056#define DEFAULT_FONT "Sans 15"
Sebastian Dröge291a8042011-04-09 09:50:23 +020057#define DEFAULT_SILENT FALSE
Sebastian Drögeeaf01f92011-04-09 10:03:00 +020058#define DEFAULT_LAST_MESSAGE NULL
Stefan Koste0137362009-04-16 18:36:13 +030059
Stefan Koste0137362009-04-16 18:36:13 +030060/* generic templates */
61static GstStaticPadTemplate fps_display_sink_template =
62GST_STATIC_PAD_TEMPLATE ("sink",
63 GST_PAD_SINK,
64 GST_PAD_ALWAYS,
65 GST_STATIC_CAPS_ANY);
66
67GST_DEBUG_CATEGORY_STATIC (fps_display_sink_debug);
68#define GST_CAT_DEFAULT fps_display_sink_debug
69
benjamin gaignard588db0b2011-02-28 11:51:54 +010070#define DEFAULT_SYNC TRUE
71
Stefan Koste0137362009-04-16 18:36:13 +030072enum
73{
Thiago Santos57b9ce72010-06-24 12:37:36 -030074 /* FILL ME */
75 SIGNAL_FPS_MEASUREMENTS,
76 LAST_SIGNAL
77};
78
79enum
80{
Sebastian Drögea7cbd202011-04-08 14:08:10 +020081 PROP_0,
82 PROP_SYNC,
83 PROP_TEXT_OVERLAY,
84 PROP_VIDEO_SINK,
85 PROP_FPS_UPDATE_INTERVAL,
86 PROP_MAX_FPS,
87 PROP_MIN_FPS,
88 PROP_SIGNAL_FPS_MEASUREMENTS,
89 PROP_FRAMES_DROPPED,
90 PROP_FRAMES_RENDERED,
Sebastian Drögeeaf01f92011-04-09 10:03:00 +020091 PROP_SILENT,
92 PROP_LAST_MESSAGE
Thiago Santosdc8f1db2010-06-24 10:23:02 -030093 /* FILL ME */
Stefan Koste0137362009-04-16 18:36:13 +030094};
95
96static GstBinClass *parent_class = NULL;
97
98static GstStateChangeReturn fps_display_sink_change_state (GstElement * element,
99 GstStateChange transition);
100static void fps_display_sink_set_property (GObject * object, guint prop_id,
101 const GValue * value, GParamSpec * pspec);
102static void fps_display_sink_get_property (GObject * object, guint prop_id,
103 GValue * value, GParamSpec * pspec);
104static void fps_display_sink_dispose (GObject * object);
105
Rob Clark60c963f2010-08-23 09:51:25 -0500106static gboolean display_current_fps (gpointer data);
107
Thiago Santos57b9ce72010-06-24 12:37:36 -0300108static guint fpsdisplaysink_signals[LAST_SIGNAL] = { 0 };
109
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200110static GParamSpec *pspec_last_message = NULL;
111
Stefan Koste0137362009-04-16 18:36:13 +0300112static void
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100113fps_display_sink_class_init (GstFPSDisplaySinkClass * klass)
Stefan Koste0137362009-04-16 18:36:13 +0300114{
115 GObjectClass *gobject_klass = G_OBJECT_CLASS (klass);
116 GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (klass);
Stefan Koste0137362009-04-16 18:36:13 +0300117
118 parent_class = g_type_class_peek_parent (klass);
119
120 gobject_klass->set_property = fps_display_sink_set_property;
121 gobject_klass->get_property = fps_display_sink_get_property;
122 gobject_klass->dispose = fps_display_sink_dispose;
123
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200124 g_object_class_install_property (gobject_klass, PROP_SYNC,
Stefan Koste0137362009-04-16 18:36:13 +0300125 g_param_spec_boolean ("sync",
Philippe Normand9c031492009-12-15 13:08:08 +0100126 "Sync", "Sync on the clock (if the internally used sink doesn't "
benjamin gaignard588db0b2011-02-28 11:51:54 +0100127 "have this property it will be ignored", DEFAULT_SYNC,
Stefan Kost1ae52722009-04-30 13:45:30 +0300128 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
Stefan Koste0137362009-04-16 18:36:13 +0300129
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200130 g_object_class_install_property (gobject_klass, PROP_TEXT_OVERLAY,
Stefan Koste0137362009-04-16 18:36:13 +0300131 g_param_spec_boolean ("text-overlay",
132 "text-overlay",
Tim-Philipp Müllerec0c8742009-12-13 13:53:14 +0000133 "Whether to use text-overlay", TRUE,
Stefan Kost1ae52722009-04-30 13:45:30 +0300134 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
Stefan Koste0137362009-04-16 18:36:13 +0300135
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200136 g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK,
Philippe Normand4111d632009-12-10 22:49:13 -0300137 g_param_spec_object ("video-sink",
138 "video-sink",
Thiago Santos87bfdef2009-12-17 11:04:28 -0300139 "Video sink to use (Must only be called on NULL state)",
Thiago Santosdfec8f72010-06-24 10:24:27 -0300140 GST_TYPE_ELEMENT, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
Philippe Normand4111d632009-12-10 22:49:13 -0300141
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200142 g_object_class_install_property (gobject_klass, PROP_FPS_UPDATE_INTERVAL,
Thiago Santosdc8f1db2010-06-24 10:23:02 -0300143 g_param_spec_int ("fps-update-interval", "Fps update interval",
144 "Time between consecutive frames per second measures and update "
145 " (in ms). Should be set on NULL state", 1, G_MAXINT,
Thiago Santosdfec8f72010-06-24 10:24:27 -0300146 DEFAULT_FPS_UPDATE_INTERVAL_MS,
147 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
Thiago Santos57b9ce72010-06-24 12:37:36 -0300148
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200149 g_object_class_install_property (gobject_klass, PROP_MAX_FPS,
Thiago Santosf78bf262010-06-24 10:59:32 -0300150 g_param_spec_double ("max-fps", "Max fps",
151 "Maximum fps rate measured. Reset when going from NULL to READY."
152 "-1 means no measurement has yet been done", -1, G_MAXDOUBLE, -1,
153 G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
Thiago Santos57b9ce72010-06-24 12:37:36 -0300154
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200155 g_object_class_install_property (gobject_klass, PROP_MIN_FPS,
Thiago Santosf78bf262010-06-24 10:59:32 -0300156 g_param_spec_double ("min-fps", "Min fps",
157 "Minimum fps rate measured. Reset when going from NULL to READY."
158 "-1 means no measurement has yet been done", -1, G_MAXDOUBLE, -1,
159 G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
Thiago Santosdc8f1db2010-06-24 10:23:02 -0300160
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200161 g_object_class_install_property (gobject_klass, PROP_FRAMES_DROPPED,
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100162 g_param_spec_uint ("frames-dropped", "dropped frames",
163 "Number of frames dropped by the sink", 0, G_MAXUINT, 0,
164 G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
165
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200166 g_object_class_install_property (gobject_klass, PROP_FRAMES_RENDERED,
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100167 g_param_spec_uint ("frames-rendered", "rendered frames",
168 "Number of frames rendered", 0, G_MAXUINT, 0,
169 G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
170
Sebastian Dröge291a8042011-04-09 09:50:23 +0200171 g_object_class_install_property (gobject_klass, PROP_SILENT,
172 g_param_spec_boolean ("silent", "enable stdout output",
173 "Don't produce last_message events", DEFAULT_SILENT,
Philippe Normand26bc5532011-04-07 15:15:57 +0200174 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100175
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200176 g_object_class_install_property (gobject_klass, PROP_SIGNAL_FPS_MEASUREMENTS,
Thiago Santos57b9ce72010-06-24 12:37:36 -0300177 g_param_spec_boolean ("signal-fps-measurements",
178 "Signal fps measurements",
179 "If the fps-measurements signal should be emited.",
180 DEFAULT_SIGNAL_FPS_MEASUREMENTS,
Thiago Santosc4709182010-07-01 16:28:57 -0300181 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
Thiago Santos57b9ce72010-06-24 12:37:36 -0300182
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200183 pspec_last_message = g_param_spec_string ("last-message", "Last Message",
184 "The message describing current status", DEFAULT_LAST_MESSAGE,
185 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
186 g_object_class_install_property (gobject_klass, PROP_LAST_MESSAGE,
187 pspec_last_message);
188
Thiago Santos57b9ce72010-06-24 12:37:36 -0300189 /**
190 * GstFPSDisplaySink::fps-measurements:
191 * @fpsdisplaysink: a #GstFPSDisplaySink
192 * @fps: The current measured fps
193 * @droprate: The rate at which buffers are being dropped
194 * @avgfps: The average fps
195 *
196 * Signals the application about the measured fps
197 *
198 * Since: 0.10.20
199 */
200 fpsdisplaysink_signals[SIGNAL_FPS_MEASUREMENTS] =
201 g_signal_new ("fps-measurements", G_TYPE_FROM_CLASS (klass),
202 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
203 __gst_debugutils_marshal_VOID__DOUBLE_DOUBLE_DOUBLE,
204 G_TYPE_NONE, 3, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
205
Stefan Koste0137362009-04-16 18:36:13 +0300206 gstelement_klass->change_state = fps_display_sink_change_state;
207
Stefan Koste0137362009-04-16 18:36:13 +0300208 gst_element_class_add_pad_template (gstelement_klass,
209 gst_static_pad_template_get (&fps_display_sink_template));
210
Benjamin Otte775c7582010-03-18 17:30:26 +0100211 gst_element_class_set_details_simple (gstelement_klass,
212 "Measure and show framerate on videosink", "Sink/Video",
213 "Shows the current frame-rate and drop-rate of the videosink as overlay or text on stdout",
214 "Zeeshan Ali <zeeshan.ali@nokia.com>, Stefan Kost <stefan.kost@nokia.com>");
Stefan Koste0137362009-04-16 18:36:13 +0300215}
216
Stefan Koste0137362009-04-16 18:36:13 +0300217static gboolean
Stefan Kost193f6382009-04-20 10:24:37 +0300218on_video_sink_data_flow (GstPad * pad, GstMiniObject * mini_obj,
Stefan Koste0137362009-04-16 18:36:13 +0300219 gpointer user_data)
220{
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100221 GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (user_data);
Stefan Koste0137362009-04-16 18:36:13 +0300222
223#if 0
224 if (GST_IS_BUFFER (mini_obj)) {
225 GstBuffer *buf = GST_BUFFER_CAST (mini_obj);
226
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100227 if (GST_CLOCK_TIME_IS_VALID (self->next_ts)) {
228 if (GST_BUFFER_TIMESTAMP (buf) <= self->next_ts) {
229 self->frames_rendered++;
Stefan Koste0137362009-04-16 18:36:13 +0300230 } else {
Stefan Kost193f6382009-04-20 10:24:37 +0300231 GST_WARNING_OBJECT (self, "dropping frame : ts %" GST_TIME_FORMAT
232 " < expected_ts %" GST_TIME_FORMAT,
233 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100234 GST_TIME_ARGS (self->next_ts));
235 self->frames_dropped++;
Stefan Koste0137362009-04-16 18:36:13 +0300236 }
237 } else {
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100238 self->frames_rendered++;
Stefan Koste0137362009-04-16 18:36:13 +0300239 }
240 } else
241#endif
242 if (GST_IS_EVENT (mini_obj)) {
243 GstEvent *ev = GST_EVENT_CAST (mini_obj);
244
245 if (GST_EVENT_TYPE (ev) == GST_EVENT_QOS) {
246 GstClockTimeDiff diff;
247 GstClockTime ts;
248
249 gst_event_parse_qos (ev, NULL, &diff, &ts);
Stefan Koste0137362009-04-16 18:36:13 +0300250 if (diff <= 0.0) {
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100251 g_atomic_int_inc (&self->frames_rendered);
Stefan Koste0137362009-04-16 18:36:13 +0300252 } else {
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100253 g_atomic_int_inc (&self->frames_dropped);
Stefan Koste0137362009-04-16 18:36:13 +0300254 }
Rob Clark60c963f2010-08-23 09:51:25 -0500255
Stefan Kost10776e42010-08-24 11:26:52 +0300256 ts = gst_util_get_timestamp ();
257 if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (self->start_ts))) {
Lasse Laukkanen4ebdd9b2010-09-23 14:15:08 +0300258 self->interval_ts = self->last_ts = self->start_ts = ts;
Stefan Kost10776e42010-08-24 11:26:52 +0300259 }
260 if (GST_CLOCK_DIFF (self->interval_ts, ts) > self->fps_update_interval) {
261 display_current_fps (self);
262 self->interval_ts = ts;
Rob Clark60c963f2010-08-23 09:51:25 -0500263 }
Stefan Koste0137362009-04-16 18:36:13 +0300264 }
265 }
266 return TRUE;
267}
268
269static void
Thiago Santosd2dce722009-12-16 18:32:42 -0300270update_sub_sync (GstElement * sink, gpointer data)
Philippe Normand4111d632009-12-10 22:49:13 -0300271{
Philippe Normand9c031492009-12-15 13:08:08 +0100272 /* Some sinks (like autovideosink) don't have the sync property so
273 * we check it exists before setting it to avoid a warning at
274 * runtime. */
275 if (g_object_class_find_property (G_OBJECT_GET_CLASS (sink), "sync"))
276 g_object_set (sink, "sync", *((gboolean *) data), NULL);
277 else
278 GST_WARNING ("Internal sink doesn't have sync property");
Philippe Normand4111d632009-12-10 22:49:13 -0300279}
280
281static void
Thiago Santosd2dce722009-12-16 18:32:42 -0300282fps_display_sink_update_sink_sync (GstFPSDisplaySink * self)
283{
284 GstIterator *iterator;
285
286 if (self->video_sink == NULL)
287 return;
288
289 if (GST_IS_BIN (self->video_sink)) {
290 iterator = gst_bin_iterate_sinks (GST_BIN (self->video_sink));
291 gst_iterator_foreach (iterator, (GFunc) update_sub_sync,
292 (void *) &self->sync);
293 gst_iterator_free (iterator);
294 } else
295 update_sub_sync (self->video_sink, (void *) &self->sync);
296
297}
298
299static void
Philippe Normand4111d632009-12-10 22:49:13 -0300300update_video_sink (GstFPSDisplaySink * self, GstElement * video_sink)
Stefan Koste0137362009-04-16 18:36:13 +0300301{
Stefan Kost1ae52722009-04-30 13:45:30 +0300302 GstPad *sink_pad;
Stefan Koste0137362009-04-16 18:36:13 +0300303
Philippe Normand4111d632009-12-10 22:49:13 -0300304 if (self->video_sink) {
Stefan Koste0137362009-04-16 18:36:13 +0300305
Philippe Normand4111d632009-12-10 22:49:13 -0300306 /* remove pad probe */
307 sink_pad = gst_element_get_static_pad (self->video_sink, "sink");
308 gst_pad_remove_data_probe (sink_pad, self->data_probe_id);
309 gst_object_unref (sink_pad);
310 self->data_probe_id = -1;
311
Thiago Santos87bfdef2009-12-17 11:04:28 -0300312 /* remove ghost pad target */
313 gst_ghost_pad_set_target (GST_GHOST_PAD (self->ghost_pad), NULL);
Philippe Normand4111d632009-12-10 22:49:13 -0300314
315 /* remove old sink */
316 gst_bin_remove (GST_BIN (self), self->video_sink);
317 gst_object_unref (self->video_sink);
Stefan Koste0137362009-04-16 18:36:13 +0300318 }
319
Philippe Normand4111d632009-12-10 22:49:13 -0300320 /* create child elements */
321 self->video_sink = video_sink;
322
Tim-Philipp Müller7c890c02010-02-19 10:32:33 +0000323 if (self->video_sink == NULL)
324 return;
325
Thiago Santosd2dce722009-12-16 18:32:42 -0300326 fps_display_sink_update_sink_sync (self);
Stefan Koste0137362009-04-16 18:36:13 +0300327
328 /* take a ref before bin takes the ownership */
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100329 gst_object_ref (self->video_sink);
Stefan Koste0137362009-04-16 18:36:13 +0300330
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100331 gst_bin_add (GST_BIN (self), self->video_sink);
Stefan Koste0137362009-04-16 18:36:13 +0300332
Stefan Kost1ae52722009-04-30 13:45:30 +0300333 /* attach or pad probe */
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100334 sink_pad = gst_element_get_static_pad (self->video_sink, "sink");
Philippe Normand4111d632009-12-10 22:49:13 -0300335 self->data_probe_id = gst_pad_add_data_probe (sink_pad,
336 G_CALLBACK (on_video_sink_data_flow), (gpointer) self);
Stefan Koste0137362009-04-16 18:36:13 +0300337 gst_object_unref (sink_pad);
Philippe Normand4111d632009-12-10 22:49:13 -0300338}
339
340static void
341fps_display_sink_init (GstFPSDisplaySink * self,
342 GstFPSDisplaySinkClass * g_class)
343{
benjamin gaignard588db0b2011-02-28 11:51:54 +0100344 self->sync = DEFAULT_SYNC;
Thiago Santos57b9ce72010-06-24 12:37:36 -0300345 self->signal_measurements = DEFAULT_SIGNAL_FPS_MEASUREMENTS;
Philippe Normand4111d632009-12-10 22:49:13 -0300346 self->use_text_overlay = TRUE;
Stefan Kost10776e42010-08-24 11:26:52 +0300347 self->fps_update_interval = GST_MSECOND * DEFAULT_FPS_UPDATE_INTERVAL_MS;
Thiago Santos87bfdef2009-12-17 11:04:28 -0300348 self->video_sink = NULL;
Thiago Santosf78bf262010-06-24 10:59:32 -0300349 self->max_fps = -1;
350 self->min_fps = -1;
Sebastian Dröge291a8042011-04-09 09:50:23 +0200351 self->silent = DEFAULT_SILENT;
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200352 self->last_message = g_strdup (DEFAULT_LAST_MESSAGE);
Philippe Normand4111d632009-12-10 22:49:13 -0300353
Thiago Santos87bfdef2009-12-17 11:04:28 -0300354 self->ghost_pad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK);
355 gst_element_add_pad (GST_ELEMENT (self), self->ghost_pad);
Stefan Koste0137362009-04-16 18:36:13 +0300356}
357
358static gboolean
359display_current_fps (gpointer data)
360{
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100361 GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (data);
Rob Clark60c963f2010-08-23 09:51:25 -0500362 guint64 frames_rendered, frames_dropped;
363 gdouble rr, dr, average_fps;
364 gchar fps_message[256];
365 gdouble time_diff, time_elapsed;
366 GstClockTime current_ts = gst_util_get_timestamp ();
Stefan Koste0137362009-04-16 18:36:13 +0300367
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100368 frames_rendered = g_atomic_int_get (&self->frames_rendered);
369 frames_dropped = g_atomic_int_get (&self->frames_dropped);
Rob Clark60c963f2010-08-23 09:51:25 -0500370
371 if ((frames_rendered + frames_dropped) == 0) {
372 /* in case timer fired and we didn't yet get any QOS events */
Stefan Kostebc417f2009-07-20 16:20:15 +0300373 return TRUE;
Thiago Santosc4709182010-07-01 16:28:57 -0300374 }
Stefan Kostebc417f2009-07-20 16:20:15 +0300375
Rob Clark60c963f2010-08-23 09:51:25 -0500376 time_diff = (gdouble) (current_ts - self->last_ts) / GST_SECOND;
377 time_elapsed = (gdouble) (current_ts - self->start_ts) / GST_SECOND;
Stefan Koste0137362009-04-16 18:36:13 +0300378
Rob Clark60c963f2010-08-23 09:51:25 -0500379 rr = (gdouble) (frames_rendered - self->last_frames_rendered) / time_diff;
380 dr = (gdouble) (frames_dropped - self->last_frames_dropped) / time_diff;
Stefan Koste0137362009-04-16 18:36:13 +0300381
Rob Clark60c963f2010-08-23 09:51:25 -0500382 average_fps = (gdouble) frames_rendered / time_elapsed;
Stefan Koste0137362009-04-16 18:36:13 +0300383
Rob Clark60c963f2010-08-23 09:51:25 -0500384 if (self->max_fps == -1 || rr > self->max_fps) {
385 self->max_fps = rr;
386 GST_DEBUG_OBJECT (self, "Updated max-fps to %f", rr);
387 }
388 if (self->min_fps == -1 || rr < self->min_fps) {
389 self->min_fps = rr;
390 GST_DEBUG_OBJECT (self, "Updated min-fps to %f", rr);
Stefan Koste0137362009-04-16 18:36:13 +0300391 }
392
Rob Clark60c963f2010-08-23 09:51:25 -0500393 if (self->signal_measurements) {
394 GST_LOG_OBJECT (self, "Signaling measurements: fps:%f droprate:%f "
395 "avg-fps:%f", rr, dr, average_fps);
396 g_signal_emit (G_OBJECT (self),
397 fpsdisplaysink_signals[SIGNAL_FPS_MEASUREMENTS], 0, rr, dr,
398 average_fps);
399 }
400
401 /* Display on a single line to make it easier to read and import
402 * into, for example, excel.. note: it would be nice to show
403 * timestamp too.. need to check if there is a sane way to log
404 * timestamp of last rendered buffer, so we could correlate dips
405 * in framerate to certain positions in the stream.
406 */
407 if (dr == 0.0) {
408 g_snprintf (fps_message, 255,
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200409 "rendered: %" G_GUINT64_FORMAT ", dropped: %" G_GUINT64_FORMAT
410 ", current: %.2f, average: %.2f", frames_rendered, frames_dropped, rr,
Sebastian Dröge6bc160a2010-09-06 10:29:21 +0200411 average_fps);
Rob Clark60c963f2010-08-23 09:51:25 -0500412 } else {
413 g_snprintf (fps_message, 255,
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200414 "rendered: %" G_GUINT64_FORMAT ", dropped: %" G_GUINT64_FORMAT
415 ", fps: %.2f, drop rate: %.2f", frames_rendered, frames_dropped, rr,
Sebastian Dröge6bc160a2010-09-06 10:29:21 +0200416 dr);
Rob Clark60c963f2010-08-23 09:51:25 -0500417 }
418
419 if (self->use_text_overlay) {
420 g_object_set (self->text_overlay, "text", fps_message, NULL);
Philippe Normand26bc5532011-04-07 15:15:57 +0200421 }
422
Sebastian Dröge291a8042011-04-09 09:50:23 +0200423 if (!self->silent) {
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200424 GST_OBJECT_LOCK (self);
425 g_free (self->last_message);
426 self->last_message = g_strdup (fps_message);
427 GST_OBJECT_UNLOCK (self);
Tim-Philipp Müller3cadddb2011-04-14 11:28:58 +0100428#if !GLIB_CHECK_VERSION(2,26,0)
429 g_object_notify ((GObject *) self, "last-message");
430#else
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200431 g_object_notify_by_pspec ((GObject *) self, pspec_last_message);
Tim-Philipp Müller3cadddb2011-04-14 11:28:58 +0100432#endif
Rob Clark60c963f2010-08-23 09:51:25 -0500433 }
434
435 self->last_frames_rendered = frames_rendered;
436 self->last_frames_dropped = frames_dropped;
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100437 self->last_ts = current_ts;
Stefan Koste0137362009-04-16 18:36:13 +0300438
439 return TRUE;
440}
441
442static void
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100443fps_display_sink_start (GstFPSDisplaySink * self)
Stefan Koste0137362009-04-16 18:36:13 +0300444{
Tristan Matthews8d4315e2009-04-30 17:08:52 +0100445 GstPad *target_pad = NULL;
Stefan Kost1ae52722009-04-30 13:45:30 +0300446
Stefan Koste0137362009-04-16 18:36:13 +0300447 /* Init counters */
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100448 self->frames_rendered = 0;
449 self->frames_dropped = 0;
Lasse Laukkanen4ebdd9b2010-09-23 14:15:08 +0300450 self->last_frames_rendered = G_GUINT64_CONSTANT (0);
451 self->last_frames_dropped = G_GUINT64_CONSTANT (0);
Thiago Santosf78bf262010-06-24 10:59:32 -0300452 self->max_fps = -1;
453 self->min_fps = -1;
Stefan Koste0137362009-04-16 18:36:13 +0300454
Stefan Kost10776e42010-08-24 11:26:52 +0300455 /* init time stamps */
456 self->last_ts = self->start_ts = self->interval_ts = GST_CLOCK_TIME_NONE;
457
Thiago Santosa4f30d52009-12-17 07:54:04 -0300458 GST_DEBUG_OBJECT (self, "Use text-overlay? %d", self->use_text_overlay);
Stefan Kost1ae52722009-04-30 13:45:30 +0300459
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100460 if (self->use_text_overlay) {
461 if (!self->text_overlay) {
462 self->text_overlay =
Stefan Kost1ae52722009-04-30 13:45:30 +0300463 gst_element_factory_make ("textoverlay", "fps-display-text-overlay");
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100464 if (!self->text_overlay) {
Stefan Kost1ae52722009-04-30 13:45:30 +0300465 GST_WARNING_OBJECT (self, "text-overlay element could not be created");
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100466 self->use_text_overlay = FALSE;
Stefan Kost1ae52722009-04-30 13:45:30 +0300467 goto no_text_overlay;
468 }
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100469 gst_object_ref (self->text_overlay);
470 g_object_set (self->text_overlay,
Stefan Kost1ae52722009-04-30 13:45:30 +0300471 "font-desc", DEFAULT_FONT, "silent", FALSE, NULL);
Jan Schmidt90aa8ef2009-05-15 10:45:45 +0100472 gst_bin_add (GST_BIN (self), self->text_overlay);
Stefan Kost1ae52722009-04-30 13:45:30 +0300473
Jan Schmidt90aa8ef2009-05-15 10:45:45 +0100474 if (!gst_element_link (self->text_overlay, self->video_sink)) {
475 GST_ERROR_OBJECT (self, "Could not link elements");
476 }
Stefan Kost1ae52722009-04-30 13:45:30 +0300477 }
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100478 target_pad = gst_element_get_static_pad (self->text_overlay, "video_sink");
Stefan Kost1ae52722009-04-30 13:45:30 +0300479 }
480no_text_overlay:
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100481 if (!self->use_text_overlay) {
482 if (self->text_overlay) {
483 gst_element_unlink (self->text_overlay, self->video_sink);
484 gst_bin_remove (GST_BIN (self), self->text_overlay);
Jan Schmidt90aa8ef2009-05-15 10:45:45 +0100485 self->text_overlay = NULL;
Stefan Kost1ae52722009-04-30 13:45:30 +0300486 }
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100487 target_pad = gst_element_get_static_pad (self->video_sink, "sink");
Stefan Kost1ae52722009-04-30 13:45:30 +0300488 }
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100489 gst_ghost_pad_set_target (GST_GHOST_PAD (self->ghost_pad), target_pad);
Stefan Kost1ae52722009-04-30 13:45:30 +0300490 gst_object_unref (target_pad);
Stefan Koste0137362009-04-16 18:36:13 +0300491}
492
493static void
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100494fps_display_sink_stop (GstFPSDisplaySink * self)
Stefan Koste0137362009-04-16 18:36:13 +0300495{
Jan Schmidt90aa8ef2009-05-15 10:45:45 +0100496 if (self->text_overlay) {
497 gst_element_unlink (self->text_overlay, self->video_sink);
498 gst_bin_remove (GST_BIN (self), self->text_overlay);
499 gst_object_unref (self->text_overlay);
500 self->text_overlay = NULL;
Jan Schmidt90aa8ef2009-05-15 10:45:45 +0100501 }
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200502
503 if (!self->silent) {
504 gchar *str;
505
506 /* print the max and minimum fps values */
507 str =
508 g_strdup_printf ("Max-fps: %0.2f, Min-fps: %0.2f", self->max_fps,
509 self->min_fps);
510 GST_OBJECT_LOCK (self);
511 g_free (self->last_message);
512 self->last_message = str;
513 GST_OBJECT_UNLOCK (self);
Tim-Philipp Müller3cadddb2011-04-14 11:28:58 +0100514#if !GLIB_CHECK_VERSION(2,26,0)
515 g_object_notify ((GObject *) self, "last-message");
516#else
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200517 g_object_notify_by_pspec ((GObject *) self, pspec_last_message);
Tim-Philipp Müller3cadddb2011-04-14 11:28:58 +0100518#endif
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200519 }
520
521 GST_OBJECT_LOCK (self);
522 g_free (self->last_message);
523 self->last_message = NULL;
524 GST_OBJECT_UNLOCK (self);
Stefan Koste0137362009-04-16 18:36:13 +0300525}
526
527static void
528fps_display_sink_dispose (GObject * object)
529{
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100530 GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (object);
Stefan Koste0137362009-04-16 18:36:13 +0300531
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100532 if (self->video_sink) {
533 gst_object_unref (self->video_sink);
534 self->video_sink = NULL;
Stefan Koste0137362009-04-16 18:36:13 +0300535 }
536
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100537 if (self->text_overlay) {
538 gst_object_unref (self->text_overlay);
539 self->text_overlay = NULL;
Stefan Koste0137362009-04-16 18:36:13 +0300540 }
541
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200542 GST_OBJECT_LOCK (self);
543 g_free (self->last_message);
544 self->last_message = NULL;
545 GST_OBJECT_UNLOCK (self);
546
Stefan Koste0137362009-04-16 18:36:13 +0300547 G_OBJECT_CLASS (parent_class)->dispose (object);
548}
549
550static void
551fps_display_sink_set_property (GObject * object, guint prop_id,
552 const GValue * value, GParamSpec * pspec)
553{
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100554 GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (object);
Stefan Koste0137362009-04-16 18:36:13 +0300555
556 switch (prop_id) {
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200557 case PROP_SYNC:
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100558 self->sync = g_value_get_boolean (value);
Thiago Santosd2dce722009-12-16 18:32:42 -0300559 fps_display_sink_update_sink_sync (self);
Stefan Koste0137362009-04-16 18:36:13 +0300560 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200561 case PROP_TEXT_OVERLAY:
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100562 self->use_text_overlay = g_value_get_boolean (value);
Stefan Koste0137362009-04-16 18:36:13 +0300563
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100564 if (self->text_overlay) {
565 if (!self->use_text_overlay) {
Stefan Kost1ae52722009-04-30 13:45:30 +0300566 GST_DEBUG_OBJECT (self, "text-overlay set to false");
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100567 g_object_set (self->text_overlay, "text", "", "silent", TRUE, NULL);
Stefan Kost1ae52722009-04-30 13:45:30 +0300568 } else {
569 GST_DEBUG_OBJECT (self, "text-overlay set to true");
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100570 g_object_set (self->text_overlay, "silent", FALSE, NULL);
Stefan Kost1ae52722009-04-30 13:45:30 +0300571 }
Stefan Koste0137362009-04-16 18:36:13 +0300572 }
573 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200574 case PROP_VIDEO_SINK:
Thiago Santos87bfdef2009-12-17 11:04:28 -0300575 /* FIXME should we add a state-lock or a lock around here?
576 * need to check if it is possible that a state change NULL->READY can
577 * happen while this code is executing on a different thread */
578 if (GST_STATE (self) != GST_STATE_NULL) {
579 g_warning ("Can't set video-sink property of fpsdisplaysink if not on "
580 "NULL state");
581 break;
582 }
Philippe Normand4111d632009-12-10 22:49:13 -0300583 update_video_sink (self, (GstElement *) g_value_get_object (value));
584 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200585 case PROP_FPS_UPDATE_INTERVAL:
Stefan Kost10776e42010-08-24 11:26:52 +0300586 self->fps_update_interval =
587 GST_MSECOND * (GstClockTime) g_value_get_int (value);
Thiago Santosdc8f1db2010-06-24 10:23:02 -0300588 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200589 case PROP_SIGNAL_FPS_MEASUREMENTS:
Thiago Santos57b9ce72010-06-24 12:37:36 -0300590 self->signal_measurements = g_value_get_boolean (value);
591 break;
Sebastian Dröge291a8042011-04-09 09:50:23 +0200592 case PROP_SILENT:
593 self->silent = g_value_get_boolean (value);
Philippe Normand26bc5532011-04-07 15:15:57 +0200594 break;
Stefan Koste0137362009-04-16 18:36:13 +0300595 default:
596 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
597 break;
598 }
599}
600
601static void
602fps_display_sink_get_property (GObject * object, guint prop_id,
603 GValue * value, GParamSpec * pspec)
604{
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100605 GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (object);
Stefan Koste0137362009-04-16 18:36:13 +0300606
607 switch (prop_id) {
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200608 case PROP_SYNC:
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100609 g_value_set_boolean (value, self->sync);
Stefan Koste0137362009-04-16 18:36:13 +0300610 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200611 case PROP_TEXT_OVERLAY:
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100612 g_value_set_boolean (value, self->use_text_overlay);
Stefan Koste0137362009-04-16 18:36:13 +0300613 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200614 case PROP_VIDEO_SINK:
Philippe Normand4111d632009-12-10 22:49:13 -0300615 g_value_set_object (value, self->video_sink);
616 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200617 case PROP_FPS_UPDATE_INTERVAL:
Stefan Kost10776e42010-08-24 11:26:52 +0300618 g_value_set_int (value, (gint) (self->fps_update_interval / GST_MSECOND));
Thiago Santosdc8f1db2010-06-24 10:23:02 -0300619 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200620 case PROP_MAX_FPS:
Thiago Santosf78bf262010-06-24 10:59:32 -0300621 g_value_set_double (value, self->max_fps);
622 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200623 case PROP_MIN_FPS:
Thiago Santosf78bf262010-06-24 10:59:32 -0300624 g_value_set_double (value, self->min_fps);
625 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200626 case PROP_FRAMES_DROPPED:
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100627 g_value_set_uint (value, g_atomic_int_get (&self->frames_dropped));
628 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200629 case PROP_FRAMES_RENDERED:
Benjamin Gaignard8c483752011-03-16 09:50:34 +0100630 g_value_set_uint (value, g_atomic_int_get (&self->frames_rendered));
631 break;
Sebastian Drögea7cbd202011-04-08 14:08:10 +0200632 case PROP_SIGNAL_FPS_MEASUREMENTS:
Thiago Santos57b9ce72010-06-24 12:37:36 -0300633 g_value_set_boolean (value, self->signal_measurements);
634 break;
Sebastian Dröge291a8042011-04-09 09:50:23 +0200635 case PROP_SILENT:
636 g_value_set_boolean (value, self->silent);
Philippe Normand26bc5532011-04-07 15:15:57 +0200637 break;
Sebastian Drögeeaf01f92011-04-09 10:03:00 +0200638 case PROP_LAST_MESSAGE:
639 GST_OBJECT_LOCK (self);
640 g_value_set_string (value, self->last_message);
641 GST_OBJECT_UNLOCK (self);
642 break;
Stefan Koste0137362009-04-16 18:36:13 +0300643 default:
644 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
645 break;
646 }
647}
648
649static GstStateChangeReturn
650fps_display_sink_change_state (GstElement * element, GstStateChange transition)
651{
652 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100653 GstFPSDisplaySink *self = GST_FPS_DISPLAY_SINK (element);
Stefan Koste0137362009-04-16 18:36:13 +0300654
655 switch (transition) {
656 case GST_STATE_CHANGE_NULL_TO_READY:
Thiago Santos87bfdef2009-12-17 11:04:28 -0300657
658 if (self->video_sink == NULL) {
659 GstElement *video_sink;
Tim-Philipp Müller7c890c02010-02-19 10:32:33 +0000660
Thiago Santos87bfdef2009-12-17 11:04:28 -0300661 GST_DEBUG_OBJECT (self, "No video sink set, creating autovideosink");
662 video_sink = gst_element_factory_make ("autovideosink",
663 "fps-display-video_sink");
664 update_video_sink (self, video_sink);
665 }
666
667 if (self->video_sink != NULL) {
668 fps_display_sink_start (self);
669 } else {
Tim-Philipp Müller7c890c02010-02-19 10:32:33 +0000670 GST_ELEMENT_ERROR (self, LIBRARY, INIT,
671 ("No video sink set and autovideosink is not available"), (NULL));
Thiago Santos87bfdef2009-12-17 11:04:28 -0300672 ret = GST_STATE_CHANGE_FAILURE;
673 }
Stefan Koste0137362009-04-16 18:36:13 +0300674 break;
Thiago Santosd2dce722009-12-16 18:32:42 -0300675 case GST_STATE_CHANGE_READY_TO_PAUSED:
676 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
677 /* reinforce our sync to children, as they might have changed
678 * internally */
679 fps_display_sink_update_sink_sync (self);
680 break;
Stefan Koste0137362009-04-16 18:36:13 +0300681 default:
682 break;
683 }
684
685 ret = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, change_state,
686 (element, transition), GST_STATE_CHANGE_SUCCESS);
687
688 switch (transition) {
689 case GST_STATE_CHANGE_READY_TO_NULL:
Stefan Kost95520972009-04-20 11:38:01 +0300690 fps_display_sink_stop (self);
Stefan Koste0137362009-04-16 18:36:13 +0300691 break;
692 default:
693 break;
694 }
695
696 return ret;
697}
698
699GType
700fps_display_sink_get_type (void)
701{
702 static GType fps_display_sink_type = 0;
703
704 if (!fps_display_sink_type) {
705 static const GTypeInfo fps_display_sink_info = {
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100706 sizeof (GstFPSDisplaySinkClass),
Stefan Kost193f6382009-04-20 10:24:37 +0300707 NULL,
Stefan Koste0137362009-04-16 18:36:13 +0300708 NULL,
709 (GClassInitFunc) fps_display_sink_class_init,
710 NULL,
711 NULL,
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100712 sizeof (GstFPSDisplaySink),
Stefan Koste0137362009-04-16 18:36:13 +0300713 0,
714 (GInstanceInitFunc) fps_display_sink_init,
715 };
Stefan Koste0137362009-04-16 18:36:13 +0300716
717 fps_display_sink_type = g_type_register_static (GST_TYPE_BIN,
Tim-Philipp Müller9b1c7c92009-05-01 02:21:10 +0100718 "GstFPSDisplaySink", &fps_display_sink_info, 0);
Tim-Philipp Müllere1010372009-05-01 01:59:56 +0100719
720 GST_DEBUG_CATEGORY_INIT (fps_display_sink_debug, "fpsdisplaysink", 0,
721 "FPS Display Sink");
Stefan Koste0137362009-04-16 18:36:13 +0300722 }
723
724 return fps_display_sink_type;
725}