1 /* Test example for instant rate changes.
2 * The example takes an input URI and runs a set of actions,
3 * seeking and pausing etc.
4 *
5 * Copyright (C) 2015-2018 Centricular Ltd
6 * @author: Edward Hervey <edward@centricular.com>
7 * @author: Jan Schmidt <jan@centricular.com>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include <glib.h>
30 #include <glib-object.h>
31 #include <glib/gprintf.h>
32 #include <gst/gst.h>
33
34 /* There are several supported scenarios
35 0) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (repeat as fast as possible for 2 sec) -> let play for 2s
36 1) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> play
37 2) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> Apply 'instant-rate-change' to 1x -> play
38 3) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> seeking (flush+key-unit) to 30s -> wait 10s -> play
39 4) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (toggle every 500ms)
40 */
41
42 #define PLAY_PAUSE_DELAY 10
43 #define IDLE_CYCLE_DELAY 2
44
45 #define TARGET_RATE_1 0.25
46 #define TARGET_RATE_2 2.0
47
48 /* Set DISABLE_AUDIO to run with video only */
49 // #define DISABLE_AUDIO
50
51 /* Set to force the use of the system clock on the pipeline */
52 // #define FORCE_SYSTEM_CLOCK
53
54 typedef struct _MyDataStruct
55 {
56 GMainLoop *mainloop;
57 GstElement *pipeline;
58 GstBus *bus;
59
60 gdouble rate;
61 gboolean paused;
62
63 guint scenario;
64 GstClockTime start;
65 GstClock *clock;
66
67 guint timeout_id;
68 guint idle_id;
69 } MyDataStruct;
70
71 static gboolean toggle_rate (MyDataStruct * data);
72
73 static gboolean
do_enable_disable_idle(MyDataStruct * data)74 do_enable_disable_idle (MyDataStruct * data)
75 {
76 if (data->idle_id) {
77 g_print ("Disabling idle handler\n");
78 g_source_remove (data->idle_id);
79 data->idle_id = 0;
80 } else {
81 g_print ("Enabling idle handler\n");
82 data->idle_id = g_idle_add ((GSourceFunc) toggle_rate, data);
83 }
84
85 return TRUE;
86 }
87
88 static gboolean
do_play_pause(MyDataStruct * data)89 do_play_pause (MyDataStruct * data)
90 {
91 data->paused = !data->paused;
92
93 switch (data->scenario) {
94 case 1:
95 g_print ("%s\n", data->paused ? "Pausing" : "Unpausing");
96 gst_element_set_state (data->pipeline,
97 data->paused ? GST_STATE_PAUSED : GST_STATE_PLAYING);
98 data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
99 (GSourceFunc) do_play_pause, data);
100 break;
101 case 2:
102 if (!data->paused) {
103 gint64 pos = GST_CLOCK_TIME_NONE;
104 gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &pos);
105
106 /* Change rate between 2x and 1x before unpausing */
107 data->rate = (data->rate == 2.0) ? 1.0 : 2.0;
108 g_print ("Switching rate to %f (position %" GST_TIME_FORMAT ")\n",
109 data->rate, GST_TIME_ARGS (pos));
110 gst_element_send_event (data->pipeline, gst_event_new_seek (data->rate,
111 GST_FORMAT_TIME, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
112 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE,
113 GST_CLOCK_TIME_NONE));
114 }
115
116 g_print ("%s\n", data->paused ? "Pausing" : "Unpausing");
117 gst_element_set_state (data->pipeline,
118 data->paused ? GST_STATE_PAUSED : GST_STATE_PLAYING);
119 data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
120 (GSourceFunc) do_play_pause, data);
121 break;
122 case 3:
123 g_print ("%s\n", data->paused ? "Pausing" : "Unpausing");
124 gst_element_set_state (data->pipeline,
125 data->paused ? GST_STATE_PAUSED : GST_STATE_PLAYING);
126 if (data->paused) {
127 /* On pause, seek to 30 seconds */
128 g_print ("Seeking to 30s\n");
129 gst_element_send_event (data->pipeline,
130 gst_event_new_seek (data->rate, GST_FORMAT_TIME,
131 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
132 GST_SEEK_TYPE_SET, 30 * GST_SECOND,
133 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE));
134 }
135 data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
136 (GSourceFunc) do_play_pause, data);
137 break;
138 default:
139 break;
140 }
141 return FALSE;
142 }
143
144 static gboolean
toggle_rate(MyDataStruct * data)145 toggle_rate (MyDataStruct * data)
146 {
147 gint64 pos = GST_CLOCK_TIME_NONE;
148 gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &pos);
149
150 /* Toggle rate between the 2 target rates */
151 if (data->rate != TARGET_RATE_2)
152 data->rate = TARGET_RATE_2;
153 else
154 data->rate = TARGET_RATE_1;
155 g_print ("Switching rate to %f (position %" GST_TIME_FORMAT ")\n", data->rate,
156 GST_TIME_ARGS (pos));
157 gst_element_send_event (data->pipeline, gst_event_new_seek (data->rate,
158 GST_FORMAT_TIME, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
159 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE,
160 GST_CLOCK_TIME_NONE));
161 return TRUE;
162 }
163
164 static void
on_preroll(MyDataStruct * data)165 on_preroll (MyDataStruct * data)
166 {
167 if (data->timeout_id != 0)
168 return; /* Already scheduled out scenario timer */
169
170 switch (data->scenario) {
171 case 0:
172 data->idle_id = g_idle_add ((GSourceFunc) toggle_rate, data);
173 data->timeout_id = g_timeout_add_seconds (IDLE_CYCLE_DELAY,
174 (GSourceFunc) do_enable_disable_idle, data);
175 break;
176 case 1:
177 case 2:
178 case 3:{
179 gint64 pos = GST_CLOCK_TIME_NONE;
180 gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &pos);
181
182 /* Change rate to 2x and play for 10 sec, pause for 10 sec */
183 data->rate = 2.0;
184 g_print ("Switching rate to %f (position %" GST_TIME_FORMAT ")\n",
185 data->rate, GST_TIME_ARGS (pos));
186 gst_element_send_event (data->pipeline, gst_event_new_seek (data->rate,
187 GST_FORMAT_TIME, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
188 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE,
189 GST_CLOCK_TIME_NONE));
190 /* Instant rate change completed, schedule play/pause */
191 data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
192 (GSourceFunc) do_play_pause, data);
193 break;
194 }
195 case 4:
196 g_timeout_add (250, (GSourceFunc) toggle_rate, data);
197 break;
198 default:
199 break;
200 }
201 }
202
203 static gboolean
_on_bus_message(GstBus * bus,GstMessage * message,gpointer userdata)204 _on_bus_message (GstBus * bus, GstMessage * message, gpointer userdata)
205 {
206 MyDataStruct *data = (MyDataStruct *) (userdata);
207
208 switch (GST_MESSAGE_TYPE (message)) {
209 case GST_MESSAGE_ERROR:{
210 GError *err = NULL;
211 gchar *name = gst_object_get_path_string (GST_MESSAGE_SRC (message));
212 gst_message_parse_error (message, &err, NULL);
213
214 g_printerr ("ERROR: from element %s: %s\n", name, err->message);
215 g_error_free (err);
216 g_free (name);
217
218 g_printf ("Stopping\n");
219 g_main_loop_quit (data->mainloop);
220 break;
221 }
222 case GST_MESSAGE_EOS:
223 g_printf ("EOS ! Stopping \n");
224 g_main_loop_quit (data->mainloop);
225 break;
226 case GST_MESSAGE_ASYNC_DONE:
227 on_preroll (data);
228 break;
229 default:
230 break;
231 }
232
233 return TRUE;
234 }
235
236 static gchar *
cmdline_to_uri(const gchar * arg)237 cmdline_to_uri (const gchar * arg)
238 {
239 if (gst_uri_is_valid (arg))
240 return g_strdup (arg);
241
242 return gst_filename_to_uri (arg, NULL);
243 }
244
245 static void
print_usage(const char * arg0)246 print_usage (const char *arg0)
247 {
248 g_print
249 ("Usage: %s <0-4> URI\nSelect test scenario 0 to 4, and supply a URI to test\n",
250 arg0);
251 g_print ("Scenarios:\n"
252 " 0) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (repeat as fast as possible for 2 sec) -> let play for 2s\n"
253 " 1) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> play\n"
254 " 2) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> Apply 'instant-rate-change' to 1x -> play\n"
255 " 3) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> seeking (flush+key-unit) to 30s -> wait 10s -> play\n"
256 " 4) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (toggle every 500ms)\n");
257 }
258
259 int
main(int argc,gchar ** argv)260 main (int argc, gchar ** argv)
261 {
262 GstBus *bus;
263 MyDataStruct *data;
264 gchar *uri;
265 #ifdef FORCE_SYSTEM_CLOCK
266 GstClock *clock;
267 #endif
268
269 gst_init (&argc, &argv);
270
271 data = g_new0 (MyDataStruct, 1);
272
273 if (argc < 3) {
274 print_usage (argv[0]);
275 return 1;
276 }
277
278 data->scenario = atoi (argv[1]);
279 uri = cmdline_to_uri (argv[2]);
280 if (data->scenario > 4 || uri == NULL) {
281 print_usage (argv[0]);
282 return 1;
283 }
284
285 data->pipeline = gst_element_factory_make ("playbin", NULL);
286 if (data->pipeline == NULL) {
287 g_printerr ("Failed to create playbin element. Aborting");
288 return 1;
289 }
290 #ifdef FORCE_SYSTEM_CLOCK
291 clock = gst_system_clock_obtain ();
292 gst_pipeline_use_clock (GST_PIPELINE (data->pipeline), clock);
293 #endif
294
295 #ifdef DISABLE_AUDIO
296 g_object_set (data->pipeline, "flags", 0x00000615, NULL);
297 #endif
298 g_object_set (data->pipeline, "uri", uri, NULL);
299 g_free (uri);
300
301 /* Put a bus handler */
302 bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
303 gst_bus_add_watch (bus, _on_bus_message, data);
304
305 /* Start pipeline */
306 data->mainloop = g_main_loop_new (NULL, TRUE);
307 gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
308 g_main_loop_run (data->mainloop);
309
310 gst_element_set_state (data->pipeline, GST_STATE_NULL);
311
312 gst_object_unref (data->pipeline);
313 gst_object_unref (bus);
314
315 return 0;
316 }
317