No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 #include "config.h"
4
5 #include <fcntl.h>
6 #include <math.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <unistd.h>
10
11 #define GST_USE_UNSTABLE_API
12 #include <gst/gst.h>
13
14 #include "shell-recorder-src.h"
15 #include "shell-recorder.h"
16 #include "shell-screen-grabber.h"
17
18 #include <clutter/x11/clutter-x11.h>
19 #include <X11/extensions/Xfixes.h>
20
21 typedef enum {
22 RECORDER_STATE_CLOSED,
23 RECORDER_STATE_RECORDING
24 } RecorderState;
25
26 typedef struct _RecorderPipeline RecorderPipeline;
27
28 struct _ShellRecorderClass
29 {
30 GObjectClass parent_class;
31 };
32
33 struct _ShellRecorder {
34 GObject parent;
35
36 /* A "maximum" amount of memory to use for buffering. This is used
37 * to alert the user that they are filling up memory rather than
38 * any that actually affects recording. (In kB)
39 */
40 guint memory_target;
41 guint memory_used; /* Current memory used. (In kB) */
42
43 RecorderState state;
44
45 ClutterStage *stage;
46 int stage_width;
47 int stage_height;
48
49 ShellScreenGrabber *grabber;
50
51 gboolean have_pointer;
52 int pointer_x;
53 int pointer_y;
54
55 gboolean have_xfixes;
56 int xfixes_event_base;
57
58 CoglHandle recording_icon; /* icon shown while playing */
59
60 cairo_surface_t *cursor_image;
61 int cursor_hot_x;
62 int cursor_hot_y;
63
64 gboolean only_paint; /* Used to temporarily suppress recording */
65
66 int framerate;
67 char *pipeline_description;
68 char *filename;
69
70 /* We might have multiple pipelines that are finishing encoding
71 * to go along with the current pipeline where we are recording.
72 */
73 RecorderPipeline *current_pipeline; /* current pipeline */
74 GSList *pipelines; /* all pipelines */
75
76 GstClockTime start_time; /* When we started recording */
77 GstClockTime last_frame_time; /* Timestamp for the last frame */
78
79 /* GSource IDs for different timeouts and idles */
80 guint redraw_timeout;
81 guint redraw_idle;
82 guint update_memory_used_timeout;
83 guint update_pointer_timeout;
84 guint repaint_hook_id;
85 };
86
87 struct _RecorderPipeline
88 {
89 ShellRecorder *recorder;
90 GstElement *pipeline;
91 GstElement *src;
92 int outfile;
93 };
94
95 static void recorder_set_stage (ShellRecorder *recorder,
96 ClutterStage *stage);
97 static void recorder_set_framerate (ShellRecorder *recorder,
98 int framerate);
99 static void recorder_set_pipeline (ShellRecorder *recorder,
100 const char *pipeline);
101 static void recorder_set_filename (ShellRecorder *recorder,
102 const char *filename);
103
104 static void recorder_pipeline_set_caps (RecorderPipeline *pipeline);
105 static void recorder_pipeline_closed (RecorderPipeline *pipeline);
106
107 enum {
108 PROP_0,
109 PROP_STAGE,
110 PROP_FRAMERATE,
111 PROP_PIPELINE,
112 PROP_FILENAME
113 };
114
115 G_DEFINE_TYPE(ShellRecorder, shell_recorder, G_TYPE_OBJECT);
116
117 /* The default value of the target frame rate; we'll never record more
118 * than this many frames per second, though we may record less if the
119 * screen isn't being redrawn. 30 is a compromise between smoothness
120 * and the size of the recording.
121 */
122 #define DEFAULT_FRAMES_PER_SECOND 30
123
124 /* The time (in milliseconds) between querying the server for the cursor
125 * position.
126 */
127 #define UPDATE_POINTER_TIME 100
128
129 /* The time we wait (in milliseconds) before redrawing when the memory used
130 * changes.
131 */
132 #define UPDATE_MEMORY_USED_DELAY 500
133
134 /* Maximum time between frames, in milliseconds. If we don't send data
135 * for a long period of time, then when we send the next frame, a lot
136 * of work can be created for the encoder to do, so we want to force a
137 * periodic redraw when nothing happen.
138 */
139 #define MAXIMUM_PAUSE_TIME 1000
140
141 /* The default pipeline. videorate is used to give a constant stream of
142 * frames to theora even if there is a pause because nothing is moving.
143 * (Theora does have some support for frames at non-uniform times, but
144 * things seem to break down if there are large gaps.)
145 */
146 #define DEFAULT_PIPELINE "vp8enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmux"
147
148 /* If we can find the amount of memory on the machine, we use half
149 * of that for memory_target, otherwise, we use this value, in kB.
150 */
151 #define DEFAULT_MEMORY_TARGET (512*1024)
152
153 /* Create an emblem to show at the lower-left corner of the stage while
154 * recording. The emblem is drawn *after* we record the frame so doesn't
155 * show up in the frame.
156 */
157 static CoglHandle
158 create_recording_icon (void)
159 {
160 cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 32, 32);
161 cairo_t *cr;
162 cairo_pattern_t *pat;
163 CoglHandle texture;
164
165 cr = cairo_create (surface);
166
167 /* clear to transparent */
168 cairo_save (cr);
169 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
170 cairo_paint (cr);
171 cairo_restore (cr);
172
173 /* radial "glow" */
174 pat = cairo_pattern_create_radial (16, 16, 6,
175 16, 16, 14);
176 cairo_pattern_add_color_stop_rgba (pat, 0.0,
177 1, 0, 0, 1); /* opaque red */
178 cairo_pattern_add_color_stop_rgba (pat, 1.0,
179 1, 0, 0, 0); /* transparent red */
180
181 cairo_set_source (cr, pat);
182 cairo_paint (cr);
183 cairo_pattern_destroy (pat);
184
185 /* red circle */
186 cairo_arc (cr, 16, 16, 8,
187 0, 2 * M_PI);
188 cairo_set_source_rgb (cr, 1, 0, 0);
189 cairo_fill (cr);
190
191 cairo_destroy (cr);
192
193 texture = cogl_texture_new_from_data (32, 32,
194 COGL_TEXTURE_NONE,
195 CLUTTER_CAIRO_FORMAT_ARGB32,
196 COGL_PIXEL_FORMAT_ANY,
197 cairo_image_surface_get_stride (surface),
198 cairo_image_surface_get_data (surface));
199 cairo_surface_destroy (surface);
200
201 return texture;
202 }
203
204 static guint
205 get_memory_target (void)
206 {
207 FILE *f;
208
209 /* Really simple "get amount of memory on the machine" if it
210 * doesn't work, you just get the default memory target.
211 */
212 f = fopen("/proc/meminfo", "r");
213 if (!f)
214 return DEFAULT_MEMORY_TARGET;
215
216 while (!feof(f))
217 {
218 gchar line_buffer[1024];
219 guint mem_total;
220 if (fscanf(f, "MemTotal: %u", &mem_total) == 1)
221 {
222 fclose(f);
223 return mem_total / 2;
224 }
225 /* Skip to the next line and discard what we read */
226 if (fgets(line_buffer, sizeof(line_buffer), f) == NULL)
227 break;
228 }
229
230 fclose(f);
231
232 return DEFAULT_MEMORY_TARGET;
233 }
234
235 /*
236 * Used to force full stage redraws during recording to avoid artifacts
237 *
238 * Note: That this will cause the stage to be repainted on
239 * every animation frame even if the frame wouldn't normally cause any new
240 * drawing
241 */
242 static gboolean
243 recorder_repaint_hook (gpointer data)
244 {
245 ClutterActor *stage = data;
246 clutter_actor_queue_redraw (stage);
247
248 return TRUE;
249 }
250
251 static void
252 shell_recorder_init (ShellRecorder *recorder)
253 {
254 /* Calling gst_init() is a no-op if GStreamer was previously initialized */
255 gst_init (NULL, NULL);
256
257 shell_recorder_src_register ();
258
259 recorder->recording_icon = create_recording_icon ();
260 recorder->memory_target = get_memory_target();
261
262 recorder->grabber = shell_screen_grabber_new ();
263
264 recorder->state = RECORDER_STATE_CLOSED;
265 recorder->framerate = DEFAULT_FRAMES_PER_SECOND;
266 }
267
268 static void
269 shell_recorder_finalize (GObject *object)
270 {
271 ShellRecorder *recorder = SHELL_RECORDER (object);
272 GSList *l;
273
274 for (l = recorder->pipelines; l; l = l->next)
275 {
276 RecorderPipeline *pipeline = l->data;
277
278 /* Remove the back-reference. The pipeline will be freed
279 * when it finishes. (Or when the process exits, but that's
280 * out of our control.)
281 */
282 pipeline->recorder = NULL;
283 }
284
285 if (recorder->update_memory_used_timeout)
286 g_source_remove (recorder->update_memory_used_timeout);
287
288 if (recorder->cursor_image)
289 cairo_surface_destroy (recorder->cursor_image);
290
291 recorder_set_stage (recorder, NULL);
292 recorder_set_pipeline (recorder, NULL);
293 recorder_set_filename (recorder, NULL);
294
295 g_object_unref (recorder->grabber);
296
297 cogl_handle_unref (recorder->recording_icon);
298
299 G_OBJECT_CLASS (shell_recorder_parent_class)->finalize (object);
300 }
301
302 static void
303 recorder_on_stage_destroy (ClutterActor *actor,
304 ShellRecorder *recorder)
305 {
306 recorder_set_stage (recorder, NULL);
307 }
308
309 /* Add together the memory used by all pipelines; both the
310 * currently recording pipeline and pipelines finishing
311 * recording asynchronously.
312 */
313 static void
314 recorder_update_memory_used (ShellRecorder *recorder,
315 gboolean repaint)
316 {
317 guint memory_used = 0;
318 GSList *l;
319
320 for (l = recorder->pipelines; l; l = l->next)
321 {
322 RecorderPipeline *pipeline = l->data;
323 guint pipeline_memory_used;
324
325 g_object_get (pipeline->src,
326 "memory-used", &pipeline_memory_used,
327 NULL);
328 memory_used += pipeline_memory_used;
329 }
330
331 if (memory_used != recorder->memory_used)
332 {
333 recorder->memory_used = memory_used;
334 if (repaint)
335 {
336 /* In other cases we just queue a redraw even if we only need
337 * to repaint and not redraw a frame, but having changes in
338 * memory usage cause frames to be painted and memory used
339 * seems like a bad idea.
340 */
341 recorder->only_paint = TRUE;
342 clutter_stage_ensure_redraw (recorder->stage);
343 recorder->only_paint = FALSE;
344 }
345 }
346 }
347
348 /* Timeout used to avoid not drawing for more than MAXIMUM_PAUSE_TIME
349 */
350 static gboolean
351 recorder_redraw_timeout (gpointer data)
352 {
353 ShellRecorder *recorder = data;
354
355 recorder->redraw_timeout = 0;
356 clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
357
358 return FALSE;
359 }
360
361 static void
362 recorder_add_redraw_timeout (ShellRecorder *recorder)
363 {
364 if (recorder->redraw_timeout == 0)
365 {
366 recorder->redraw_timeout = g_timeout_add (MAXIMUM_PAUSE_TIME,
367 recorder_redraw_timeout,
368 recorder);
369 }
370 }
371
372 static void
373 recorder_remove_redraw_timeout (ShellRecorder *recorder)
374 {
375 if (recorder->redraw_timeout != 0)
376 {
377 g_source_remove (recorder->redraw_timeout);
378 recorder->redraw_timeout = 0;
379 }
380 }
381
382 static void
383 recorder_fetch_cursor_image (ShellRecorder *recorder)
384 {
385 XFixesCursorImage *cursor_image;
386 guchar *data;
387 int stride;
388 int i, j;
389
390 if (!recorder->have_xfixes)
391 return;
392
393 cursor_image = XFixesGetCursorImage (clutter_x11_get_default_display ());
394 if (!cursor_image)
395 return;
396
397 recorder->cursor_hot_x = cursor_image->xhot;
398 recorder->cursor_hot_y = cursor_image->yhot;
399
400 recorder->cursor_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
401 cursor_image->width,
402 cursor_image->height);
403
404 /* The pixel data (in typical Xlib breakage) is longs even on
405 * 64-bit platforms, so we have to data-convert there. For simplicity,
406 * just do it always
407 */
408 data = cairo_image_surface_get_data (recorder->cursor_image);
409 stride = cairo_image_surface_get_stride (recorder->cursor_image);
410 for (i = 0; i < cursor_image->height; i++)
411 for (j = 0; j < cursor_image->width; j++)
412 *(guint32 *)(data + i * stride + 4 * j) = cursor_image->pixels[i * cursor_image->width + j];
413
414 cairo_surface_mark_dirty (recorder->cursor_image);
415
416 XFree (cursor_image);
417 }
418
419 /* Overlay the cursor image on the frame. We draw the cursor image
420 * into the host-memory buffer after we've captured the frame. An
421 * alternate approach would be to turn off the cursor while recording
422 * and draw the cursor ourselves with GL, but then we'd need to figure
423 * out what the cursor looks like, or hard-code a non-system cursor.
424 */
425 static void
426 recorder_draw_cursor (ShellRecorder *recorder,
427 GstBuffer *buffer)
428 {
429 GstMapInfo info;
430 cairo_surface_t *surface;
431 cairo_t *cr;
432
433 /* We don't show a cursor unless the hot spot is in the frame; this
434 * means that sometimes we aren't going to draw a cursor even when
435 * there is a little bit overlapping within the stage */
436 if (recorder->pointer_x < 0 ||
437 recorder->pointer_y < 0 ||
438 recorder->pointer_x >= recorder->stage_width ||
439 recorder->pointer_y >= recorder->stage_height)
440 return;
441
442 if (!recorder->cursor_image)
443 recorder_fetch_cursor_image (recorder);
444
445 if (!recorder->cursor_image)
446 return;
447
448 gst_buffer_map (buffer, &info, GST_MAP_WRITE);
449 surface = cairo_image_surface_create_for_data (info.data,
450 CAIRO_FORMAT_ARGB32,
451 recorder->stage_width,
452 recorder->stage_height,
453 recorder->stage_width * 4);
454
455 cr = cairo_create (surface);
456 cairo_set_source_surface (cr,
457 recorder->cursor_image,
458 recorder->pointer_x - recorder->cursor_hot_x,
459 recorder->pointer_y - recorder->cursor_hot_y);
460 cairo_paint (cr);
461
462 cairo_destroy (cr);
463 cairo_surface_destroy (surface);
464 gst_buffer_unmap (buffer, &info);
465 }
466
467 /* Draw an overlay indicating how much of the target memory is used
468 * for buffering frames.
469 */
470 static void
471 recorder_draw_buffer_meter (ShellRecorder *recorder)
472 {
473 int fill_level;
474
475 recorder_update_memory_used (recorder, FALSE);
476
477 /* As the buffer gets more full, we go from green, to yellow, to red */
478 if (recorder->memory_used > (recorder->memory_target * 3) / 4)
479 cogl_set_source_color4f (1, 0, 0, 1);
480 else if (recorder->memory_used > recorder->memory_target / 2)
481 cogl_set_source_color4f (1, 1, 0, 1);
482 else
483 cogl_set_source_color4f (0, 1, 0, 1);
484
485 fill_level = MIN (60, (recorder->memory_used * 60) / recorder->memory_target);
486
487 /* A hollow rectangle filled from the left to fill_level */
488 cogl_rectangle (recorder->stage_width - 64, recorder->stage_height - 10,
489 recorder->stage_width - 2, recorder->stage_height - 9);
490 cogl_rectangle (recorder->stage_width - 64, recorder->stage_height - 9,
491 recorder->stage_width - (63 - fill_level), recorder->stage_height - 3);
492 cogl_rectangle (recorder->stage_width - 3, recorder->stage_height - 9,
493 recorder->stage_width - 2, recorder->stage_height - 3);
494 cogl_rectangle (recorder->stage_width - 64, recorder->stage_height - 3,
495 recorder->stage_width - 2, recorder->stage_height - 2);
496 }
497
498 /* We want to time-stamp each frame based on the actual time it was
499 * recorded. We probably should use the pipeline clock rather than
500 * gettimeofday(): that would be needed to get sync'ed audio correct.
501 * I'm not immediately sure how to handle the adjustment we currently
502 * do when pausing recording - is pausing the pipeline enough?
503 */
504 static GstClockTime
505 get_wall_time (void)
506 {
507 GTimeVal tv;
508
509 g_get_current_time (&tv);
510
511 return tv.tv_sec * 1000000000LL + tv.tv_usec * 1000LL;
512 }
513
514 /* Retrieve a frame and feed it into the pipeline
515 */
516 static void
517 recorder_record_frame (ShellRecorder *recorder)
518 {
519 GstBuffer *buffer;
520 guint8 *data;
521 guint size;
522 GstClockTime now;
523
524 g_return_if_fail (recorder->current_pipeline != NULL);
525
526 /* If we get into the red zone, stop buffering new frames; 13/16 is
527 * a bit more than the 3/4 threshold for a red indicator to keep the
528 * indicator from flashing between red and yellow. */
529 if (recorder->memory_used > (recorder->memory_target * 13) / 16)
530 return;
531
532 /* Drop frames to get down to something like the target frame rate; since frames
533 * are generated with VBlank sync, we don't have full control anyways, so we just
534 * drop frames if the interval since the last frame is less than 75% of the
535 * desired inter-frame interval.
536 */
537 now = get_wall_time();
538 if (now - recorder->last_frame_time < (3 * 1000000000LL / (4 * recorder->framerate)))
539 return;
540
541 recorder->last_frame_time = now;
542
543 size = recorder->stage_width * recorder->stage_height * 4;
544
545 data = shell_screen_grabber_grab (recorder->grabber,
546 0, 0, recorder->stage_width, recorder->stage_height);
547
548 buffer = gst_buffer_new();
549 gst_buffer_insert_memory (buffer, -1,
550 gst_memory_new_wrapped (0, data, size, 0,
551 size, data, g_free));
552
553 GST_BUFFER_PTS(buffer) = now - recorder->start_time;
554
555 recorder_draw_cursor (recorder, buffer);
556
557 shell_recorder_src_add_buffer (SHELL_RECORDER_SRC (recorder->current_pipeline->src), buffer);
558 gst_buffer_unref (buffer);
559
560 /* Reset the timeout that we used to avoid an overlong pause in the stream */
561 recorder_remove_redraw_timeout (recorder);
562 recorder_add_redraw_timeout (recorder);
563 }
564
565 /* We hook in by recording each frame right after the stage is painted
566 * by clutter before glSwapBuffers() makes it visible to the user.
567 */
568 static void
569 recorder_on_stage_paint (ClutterActor *actor,
570 ShellRecorder *recorder)
571 {
572 if (recorder->state == RECORDER_STATE_RECORDING)
573 {
574 if (!recorder->only_paint)
575 recorder_record_frame (recorder);
576
577 cogl_set_source_texture (recorder->recording_icon);
578 cogl_rectangle (recorder->stage_width - 32, recorder->stage_height - 42,
579 recorder->stage_width, recorder->stage_height - 10);
580 }
581
582 if (recorder->state == RECORDER_STATE_RECORDING || recorder->memory_used != 0)
583 recorder_draw_buffer_meter (recorder);
584 }
585
586 static void
587 recorder_update_size (ShellRecorder *recorder)
588 {
589 ClutterActorBox allocation;
590
591 clutter_actor_get_allocation_box (CLUTTER_ACTOR (recorder->stage), &allocation);
592 recorder->stage_width = (int)(0.5 + allocation.x2 - allocation.x1);
593 recorder->stage_height = (int)(0.5 + allocation.y2 - allocation.y1);
594 }
595
596 static void
597 recorder_on_stage_notify_size (GObject *object,
598 GParamSpec *pspec,
599 ShellRecorder *recorder)
600 {
601 recorder_update_size (recorder);
602
603 /* This breaks the recording but tweaking the GStreamer pipeline a bit
604 * might make it work, at least if the codec can handle a stream where
605 * the frame size changes in the middle.
606 */
607 if (recorder->current_pipeline)
608 recorder_pipeline_set_caps (recorder->current_pipeline);
609 }
610
611 static gboolean
612 recorder_idle_redraw (gpointer data)
613 {
614 ShellRecorder *recorder = data;
615
616 recorder->redraw_idle = 0;
617 clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
618
619 return FALSE;
620 }
621
622 static void
623 recorder_queue_redraw (ShellRecorder *recorder)
624 {
625 /* If we just queue a redraw on every mouse motion (for example), we
626 * starve Clutter, which operates at a very low priority. So
627 * we need to queue a "low priority redraw" after timeline updates
628 */
629 if (recorder->state == RECORDER_STATE_RECORDING && recorder->redraw_idle == 0)
630 recorder->redraw_idle = g_idle_add_full (CLUTTER_PRIORITY_REDRAW + 1,
631 recorder_idle_redraw, recorder, NULL);
632 }
633
634 /* We use an event filter on the stage to get the XFixesCursorNotifyEvent
635 * and also to track cursor position (when the cursor is over the stage's
636 * input area); tracking cursor position here rather than with ClutterEvent
637 * allows us to avoid worrying about event propagation and competing
638 * signal handlers.
639 */
640 static ClutterX11FilterReturn
641 recorder_event_filter (XEvent *xev,
642 ClutterEvent *cev,
643 gpointer data)
644 {
645 ShellRecorder *recorder = data;
646
647 if (xev->xany.window != clutter_x11_get_stage_window (recorder->stage))
648 return CLUTTER_X11_FILTER_CONTINUE;
649
650 if (xev->xany.type == recorder->xfixes_event_base + XFixesCursorNotify)
651 {
652 XFixesCursorNotifyEvent *notify_event = (XFixesCursorNotifyEvent *)xev;
653
654 if (notify_event->subtype == XFixesDisplayCursorNotify)
655 {
656 if (recorder->cursor_image)
657 {
658 cairo_surface_destroy (recorder->cursor_image);
659 recorder->cursor_image = NULL;
660 }
661
662 recorder_queue_redraw (recorder);
663 }
664 }
665 else if (xev->xany.type == MotionNotify)
666 {
667 recorder->pointer_x = xev->xmotion.x;
668 recorder->pointer_y = xev->xmotion.y;
669
670 recorder_queue_redraw (recorder);
671 }
672 /* We want to track whether the pointer is over the stage
673 * window itself, and not in a child window. A "virtual"
674 * crossing is one that goes directly from ancestor to child.
675 */
676 else if (xev->xany.type == EnterNotify &&
677 (xev->xcrossing.detail != NotifyVirtual &&
678 xev->xcrossing.detail != NotifyNonlinearVirtual))
679 {
680 recorder->have_pointer = TRUE;
681 recorder->pointer_x = xev->xcrossing.x;
682 recorder->pointer_y = xev->xcrossing.y;
683
684 recorder_queue_redraw (recorder);
685 }
686 else if (xev->xany.type == LeaveNotify &&
687 (xev->xcrossing.detail != NotifyVirtual &&
688 xev->xcrossing.detail != NotifyNonlinearVirtual))
689 {
690 recorder->have_pointer = FALSE;
691 recorder->pointer_x = xev->xcrossing.x;
692 recorder->pointer_y = xev->xcrossing.y;
693
694 recorder_queue_redraw (recorder);
695 }
696
697 return CLUTTER_X11_FILTER_CONTINUE;
698 }
699
700 /* We optimize out querying the server for the pointer position if the
701 * pointer is in the input area of the ClutterStage. We track changes to
702 * that with Enter/Leave events, but we need to 100% accurate about the
703 * initial condition, which is a little involved.
704 */
705 static void
706 recorder_get_initial_cursor_position (ShellRecorder *recorder)
707 {
708 Display *xdisplay = clutter_x11_get_default_display ();
709 Window xwindow = clutter_x11_get_stage_window (recorder->stage);
710 XWindowAttributes xwa;
711 Window root, child, parent;
712 Window *children;
713 guint n_children;
714 int root_x,root_y;
715 int window_x, window_y;
716 guint mask;
717
718 XGrabServer(xdisplay);
719
720 XGetWindowAttributes (xdisplay, xwindow, &xwa);
721 XQueryTree (xdisplay, xwindow, &root, &parent, &children, &n_children);
722 XFree (children);
723
724 if (xwa.map_state == IsViewable &&
725 XQueryPointer (xdisplay, parent,
726 &root, &child, &root_x, &root_y, &window_x, &window_y, &mask) &&
727 child == xwindow)
728 {
729 /* The point of this call is not actually to translate the coordinates -
730 * we could do that ourselves using xwa.{x,y} - but rather to see if
731 * the pointer is in a child of the window, which we count as "not in
732 * window", because we aren't guaranteed to get pointer events.
733 */
734 XTranslateCoordinates(xdisplay, parent, xwindow,
735 window_x, window_y,
736 &window_x, &window_y, &child);
737 if (child == None)
738 {
739 recorder->have_pointer = TRUE;
740 recorder->pointer_x = window_x;
741 recorder->pointer_y = window_y;
742 }
743 }
744 else
745 recorder->have_pointer = FALSE;
746
747 XUngrabServer(xdisplay);
748 XFlush(xdisplay);
749
750 /* While we are at it, add mouse events to the event mask; they will
751 * be there for the stage windows that Clutter creates by default, but
752 * maybe this stage was created differently. Since we've already
753 * retrieved the event mask, it's almost free.
754 */
755 XSelectInput(xdisplay, xwindow,
756 xwa.your_event_mask | EnterWindowMask | LeaveWindowMask | PointerMotionMask);
757 }
758
759 /* When the cursor is not over the stage's input area, we query for the
760 * pointer position in a timeout.
761 */
762 static void
763 recorder_update_pointer (ShellRecorder *recorder)
764 {
765 Display *xdisplay = clutter_x11_get_default_display ();
766 Window xwindow = clutter_x11_get_stage_window (recorder->stage);
767 Window root, child;
768 int root_x,root_y;
769 int window_x, window_y;
770 guint mask;
771
772 if (recorder->have_pointer)
773 return;
774
775 if (XQueryPointer (xdisplay, xwindow,
776 &root, &child, &root_x, &root_y, &window_x, &window_y, &mask))
777 {
778 if (window_x != recorder->pointer_x || window_y != recorder->pointer_y)
779 {
780 recorder->pointer_x = window_x;
781 recorder->pointer_y = window_y;
782
783 recorder_queue_redraw (recorder);
784 }
785 }
786 }
787
788 static gboolean
789 recorder_update_pointer_timeout (gpointer data)
790 {
791 recorder_update_pointer (data);
792
793 return TRUE;
794 }
795
796 static void
797 recorder_add_update_pointer_timeout (ShellRecorder *recorder)
798 {
799 if (!recorder->update_pointer_timeout)
800 recorder->update_pointer_timeout = g_timeout_add (UPDATE_POINTER_TIME,
801 recorder_update_pointer_timeout,
802 recorder);
803 }
804
805 static void
806 recorder_remove_update_pointer_timeout (ShellRecorder *recorder)
807 {
808 if (recorder->update_pointer_timeout)
809 {
810 g_source_remove (recorder->update_pointer_timeout);
811 recorder->update_pointer_timeout = 0;
812 }
813 }
814
815 static void
816 recorder_set_stage (ShellRecorder *recorder,
817 ClutterStage *stage)
818 {
819 if (recorder->stage == stage)
820 return;
821
822 if (recorder->current_pipeline)
823 shell_recorder_close (recorder);
824
825 if (recorder->stage)
826 {
827 g_signal_handlers_disconnect_by_func (recorder->stage,
828 (void *)recorder_on_stage_destroy,
829 recorder);
830 g_signal_handlers_disconnect_by_func (recorder->stage,
831 (void *)recorder_on_stage_paint,
832 recorder);
833 g_signal_handlers_disconnect_by_func (recorder->stage,
834 (void *)recorder_on_stage_notify_size,
835 recorder);
836
837 clutter_x11_remove_filter (recorder_event_filter, recorder);
838
839 /* We don't don't deselect for cursor changes in case someone else just
840 * happened to be selecting for cursor events on the same window; sending
841 * us the events is close to free in any case.
842 */
843
844 if (recorder->redraw_idle)
845 {
846 g_source_remove (recorder->redraw_idle);
847 recorder->redraw_idle = 0;
848 }
849 }
850
851 recorder->stage = stage;
852
853 if (recorder->stage)
854 {
855 int error_base;
856
857 recorder->stage = stage;
858 g_signal_connect (recorder->stage, "destroy",
859 G_CALLBACK (recorder_on_stage_destroy), recorder);
860 g_signal_connect_after (recorder->stage, "paint",
861 G_CALLBACK (recorder_on_stage_paint), recorder);
862 g_signal_connect (recorder->stage, "notify::width",
863 G_CALLBACK (recorder_on_stage_notify_size), recorder);
864 g_signal_connect (recorder->stage, "notify::width",
865 G_CALLBACK (recorder_on_stage_notify_size), recorder);
866
867 clutter_x11_add_filter (recorder_event_filter, recorder);
868
869 recorder_update_size (recorder);
870
871 recorder->have_xfixes = XFixesQueryExtension (clutter_x11_get_default_display (),
872 &recorder->xfixes_event_base,
873 &error_base);
874 if (recorder->have_xfixes)
875 XFixesSelectCursorInput (clutter_x11_get_default_display (),
876 clutter_x11_get_stage_window (stage),
877 XFixesDisplayCursorNotifyMask);
878
879 clutter_stage_ensure_current (stage);
880
881 recorder_get_initial_cursor_position (recorder);
882 }
883 }
884
885 static void
886 recorder_set_framerate (ShellRecorder *recorder,
887 int framerate)
888 {
889 if (framerate == recorder->framerate)
890 return;
891
892 if (recorder->current_pipeline)
893 shell_recorder_close (recorder);
894
895 recorder->framerate = framerate;
896
897 g_object_notify (G_OBJECT (recorder), "framerate");
898 }
899
900 static void
901 recorder_set_pipeline (ShellRecorder *recorder,
902 const char *pipeline)
903 {
904 if (pipeline == recorder->pipeline_description ||
905 (pipeline && recorder->pipeline_description && strcmp (recorder->pipeline_description, pipeline) == 0))
906 return;
907
908 if (recorder->current_pipeline)
909 shell_recorder_close (recorder);
910
911 if (recorder->pipeline_description)
912 g_free (recorder->pipeline_description);
913
914 recorder->pipeline_description = g_strdup (pipeline);
915
916 g_object_notify (G_OBJECT (recorder), "pipeline");
917 }
918
919 static void
920 recorder_set_filename (ShellRecorder *recorder,
921 const char *filename)
922 {
923 if (filename == recorder->filename ||
924 (filename && recorder->filename && strcmp (recorder->filename, filename) == 0))
925 return;
926
927 if (recorder->current_pipeline)
928 shell_recorder_close (recorder);
929
930 if (recorder->filename)
931 g_free (recorder->filename);
932
933 recorder->filename = g_strdup (filename);
934
935 g_object_notify (G_OBJECT (recorder), "filename");
936 }
937
938 static void
939 shell_recorder_set_property (GObject *object,
940 guint prop_id,
941 const GValue *value,
942 GParamSpec *pspec)
943 {
944 ShellRecorder *recorder = SHELL_RECORDER (object);
945
946 switch (prop_id)
947 {
948 case PROP_STAGE:
949 recorder_set_stage (recorder, g_value_get_object (value));
950 break;
951 case PROP_FRAMERATE:
952 recorder_set_framerate (recorder, g_value_get_int (value));
953 break;
954 case PROP_PIPELINE:
955 recorder_set_pipeline (recorder, g_value_get_string (value));
956 break;
957 case PROP_FILENAME:
958 recorder_set_filename (recorder, g_value_get_string (value));
959 break;
960 default:
961 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
962 break;
963 }
964 }
965
966 static void
967 shell_recorder_get_property (GObject *object,
968 guint prop_id,
969 GValue *value,
970 GParamSpec *pspec)
971 {
972 ShellRecorder *recorder = SHELL_RECORDER (object);
973
974 switch (prop_id)
975 {
976 case PROP_STAGE:
977 g_value_set_object (value, G_OBJECT (recorder->stage));
978 break;
979 case PROP_FRAMERATE:
980 g_value_set_int (value, recorder->framerate);
981 break;
982 case PROP_PIPELINE:
983 g_value_set_string (value, recorder->pipeline_description);
984 break;
985 case PROP_FILENAME:
986 g_value_set_string (value, recorder->filename);
987 break;
988 default:
989 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
990 break;
991 }
992 }
993
994 static void
995 shell_recorder_class_init (ShellRecorderClass *klass)
996 {
997 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
998
999 gobject_class->finalize = shell_recorder_finalize;
1000 gobject_class->get_property = shell_recorder_get_property;
1001 gobject_class->set_property = shell_recorder_set_property;
1002
1003 g_object_class_install_property (gobject_class,
1004 PROP_STAGE,
1005 g_param_spec_object ("stage",
1006 "Stage",
1007 "Stage to record",
1008 CLUTTER_TYPE_STAGE,
1009 G_PARAM_READWRITE));
1010
1011 g_object_class_install_property (gobject_class,
1012 PROP_FRAMERATE,
1013 g_param_spec_int ("framerate",
1014 "Framerate",
1015 "Framerate used for resulting video in frames-per-second",
1016 0,
1017 G_MAXINT,
1018 DEFAULT_FRAMES_PER_SECOND,
1019 G_PARAM_READWRITE));
1020
1021 g_object_class_install_property (gobject_class,
1022 PROP_PIPELINE,
1023 g_param_spec_string ("pipeline",
1024 "Pipeline",
1025 "GStreamer pipeline description to encode recordings",
1026 NULL,
1027 G_PARAM_READWRITE));
1028
1029 g_object_class_install_property (gobject_class,
1030 PROP_FILENAME,
1031 g_param_spec_string ("filename",
1032 "Filename",
1033 "The filename template to use for output files",
1034 NULL,
1035 G_PARAM_READWRITE));
1036 }
1037
1038 /* Sets the GstCaps (video format, in this case) on the stream
1039 */
1040 static void
1041 recorder_pipeline_set_caps (RecorderPipeline *pipeline)
1042 {
1043 GstCaps *caps;
1044
1045 /* The data is always native-endian xRGB; videoconvert
1046 * doesn't support little-endian xRGB, but does support
1047 * big-endian BGRx.
1048 */
1049 caps = gst_caps_new_simple ("video/x-raw",
1050 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
1051 "format", G_TYPE_STRING, "BGRx",
1052 #else
1053 "format", G_TYPE_STRING, "xRGB",
1054 #endif
1055 "bpp", G_TYPE_INT, 32,
1056 "depth", G_TYPE_INT, 24,
1057 "framerate", GST_TYPE_FRACTION, pipeline->recorder->framerate, 1,
1058 "width", G_TYPE_INT, pipeline->recorder->stage_width,
1059 "height", G_TYPE_INT, pipeline->recorder->stage_height,
1060 NULL);
1061 g_object_set (pipeline->src, "caps", caps, NULL);
1062 gst_caps_unref (caps);
1063 }
1064
1065 /* Augments the supplied pipeline with the source elements: the actual
1066 * ShellRecorderSrc element where we inject frames then additional elements
1067 * to convert the output into something palatable.
1068 */
1069 static gboolean
1070 recorder_pipeline_add_source (RecorderPipeline *pipeline)
1071 {
1072 GstPad *sink_pad = NULL, *src_pad = NULL;
1073 gboolean result = FALSE;
1074 GstElement *videoconvert;
1075
1076 sink_pad = gst_bin_find_unlinked_pad (GST_BIN (pipeline->pipeline), GST_PAD_SINK);
1077 if (sink_pad == NULL)
1078 {
1079 g_warning("ShellRecorder: pipeline has no unlinked sink pad");
1080 goto out;
1081 }
1082
1083 pipeline->src = gst_element_factory_make ("shellrecordersrc", NULL);
1084 if (pipeline->src == NULL)
1085 {
1086 g_warning ("Can't create recorder source element");
1087 goto out;
1088 }
1089 gst_bin_add (GST_BIN (pipeline->pipeline), pipeline->src);
1090
1091 recorder_pipeline_set_caps (pipeline);
1092
1093 /* The videoconvert element is a generic converter; it will convert
1094 * our supplied fixed format data into whatever the encoder wants
1095 */
1096 videoconvert = gst_element_factory_make ("videoconvert", NULL);
1097 if (!videoconvert)
1098 {
1099 g_warning("Can't create videoconvert element");
1100 goto out;
1101 }
1102 gst_bin_add (GST_BIN (pipeline->pipeline), videoconvert);
1103
1104 gst_element_link_many (pipeline->src, videoconvert, NULL);
1105 src_pad = gst_element_get_static_pad (videoconvert, "src");
1106
1107 if (!src_pad)
1108 {
1109 g_warning("ShellRecorder: can't get src pad to link into pipeline");
1110 goto out;
1111 }
1112
1113 if (gst_pad_link (src_pad, sink_pad) != GST_PAD_LINK_OK)
1114 {
1115 g_warning("ShellRecorder: can't link to sink pad");
1116 goto out;
1117 }
1118
1119 result = TRUE;
1120
1121 out:
1122 if (sink_pad)
1123 gst_object_unref (sink_pad);
1124 if (src_pad)
1125 gst_object_unref (src_pad);
1126
1127 return result;
1128 }
1129
1130 static char *
1131 get_absolute_path (char *maybe_relative)
1132 {
1133 char *path;
1134
1135 if (g_path_is_absolute (maybe_relative))
1136 path = g_strdup (maybe_relative);
1137 else
1138 {
1139 const char *video_dir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS);
1140 path = g_build_filename (video_dir, maybe_relative, NULL);
1141 }
1142
1143 return path;
1144 }
1145
1146 /* Open a file for writing. Opening the file ourselves and using fdsink has
1147 * the advantage over filesink of being able to use O_EXCL when we want to
1148 * avoid overwriting* an existing file. Returns -1 if the file couldn't
1149 * be opened.
1150 */
1151 static int
1152 recorder_open_outfile (ShellRecorder *recorder)
1153 {
1154 const char *pattern;
1155 int flags;
1156 int outfile = -1;
1157
1158 pattern = recorder->filename;
1159 if (!pattern)
1160 return -1;
1161
1162 while (TRUE)
1163 {
1164 GString *filename = g_string_new (NULL);
1165 const char *p;
1166 char *path;
1167
1168 for (p = pattern; *p; p++)
1169 {
1170 if (*p == '%')
1171 {
1172 switch (*(p + 1))
1173 {
1174 case '%':
1175 case '\0':
1176 g_string_append_c (filename, '%');
1177 break;
1178 case 'd':
1179 {
1180 /* Appends date according to locale */
1181 GDateTime *datetime = g_date_time_new_now_local ();
1182 char *date_str = g_date_time_format (datetime, "%0x");
1183 char *s;
1184
1185 for (s = date_str; *s; s++)
1186 if (G_IS_DIR_SEPARATOR (*s))
1187 *s = '-';
1188
1189 g_string_append (filename, date_str);
1190 g_free (date_str);
1191 g_date_time_unref (datetime);
1192 }
1193 break;
1194 case 't':
1195 {
1196 /* Appends time according to locale */
1197 GDateTime *datetime = g_date_time_new_now_local ();
1198 char *time_str = g_date_time_format (datetime, "%0X");
1199 char *s;
1200
1201 for (s = time_str; *s; s++)
1202 if (G_IS_DIR_SEPARATOR (*s))
1203 *s = ':';
1204
1205 g_string_append (filename, time_str);
1206 g_free (time_str);
1207 g_date_time_unref (datetime);
1208 }
1209 break;
1210 default:
1211 g_warning ("Unknown escape %%%c in filename", *(p + 1));
1212 goto out;
1213 }
1214
1215 p++;
1216 }
1217 else
1218 g_string_append_c (filename, *p);
1219 }
1220
1221 /* If a filename is explicitly specified without %u then we assume the user
1222 * is fine with over-writing the old contents; putting %u in the default
1223 * should avoid problems with malicious symlinks.
1224 */
1225 flags = O_WRONLY | O_CREAT | O_TRUNC;
1226
1227 path = get_absolute_path (filename->str);
1228 outfile = open (path, flags, 0666);
1229 if (outfile != -1)
1230 {
1231 g_printerr ("Recording to %s\n", path);
1232
1233 g_string_free (filename, TRUE);
1234 g_free (path);
1235 goto out;
1236 }
1237
1238 if (outfile == -1 && errno != EEXIST)
1239 {
1240 g_warning ("Cannot open output file '%s': %s", filename->str, g_strerror (errno));
1241 g_string_free (filename, TRUE);
1242 g_free (path);
1243 goto out;
1244 }
1245
1246 g_string_free (filename, TRUE);
1247 g_free (path);
1248 }
1249
1250 out:
1251
1252 return outfile;
1253 }
1254
1255 /* Augments the supplied pipeline with a sink element to write to the output
1256 * file, if necessary.
1257 */
1258 static gboolean
1259 recorder_pipeline_add_sink (RecorderPipeline *pipeline)
1260 {
1261 GstPad *sink_pad = NULL, *src_pad = NULL;
1262 GstElement *fdsink;
1263 gboolean result = FALSE;
1264
1265 src_pad = gst_bin_find_unlinked_pad (GST_BIN (pipeline->pipeline), GST_PAD_SRC);
1266 if (src_pad == NULL)
1267 {
1268 /* Nothing to do - assume that we were given a complete pipeline */
1269 return TRUE;
1270 }
1271
1272 pipeline->outfile = recorder_open_outfile (pipeline->recorder);
1273 if (pipeline->outfile == -1)
1274 goto out;
1275
1276 fdsink = gst_element_factory_make ("fdsink", NULL);
1277 if (fdsink == NULL)
1278 {
1279 g_warning("Can't create fdsink element");
1280 goto out;
1281 }
1282 gst_bin_add (GST_BIN (pipeline->pipeline), fdsink);
1283 g_object_set (fdsink, "fd", pipeline->outfile, NULL);
1284
1285 sink_pad = gst_element_get_static_pad (fdsink, "sink");
1286 if (!sink_pad)
1287 {
1288 g_warning("ShellRecorder: can't get sink pad to link pipeline output");
1289 goto out;
1290 }
1291
1292 if (gst_pad_link (src_pad, sink_pad) != GST_PAD_LINK_OK)
1293 {
1294 g_warning("ShellRecorder: can't link to sink pad");
1295 goto out;
1296 }
1297
1298 result = TRUE;
1299
1300 out:
1301 if (src_pad)
1302 gst_object_unref (src_pad);
1303 if (sink_pad)
1304 gst_object_unref (sink_pad);
1305
1306 return result;
1307 }
1308
1309 static gboolean
1310 recorder_update_memory_used_timeout (gpointer data)
1311 {
1312 ShellRecorder *recorder = data;
1313 recorder->update_memory_used_timeout = 0;
1314
1315 recorder_update_memory_used (recorder, TRUE);
1316
1317 return FALSE;
1318 }
1319
1320 /* We throttle down the frequency which we recompute memory usage
1321 * and draw the buffer indicator to avoid cutting into performance.
1322 */
1323 static void
1324 recorder_pipeline_on_memory_used_changed (ShellRecorderSrc *src,
1325 GParamSpec *spec,
1326 RecorderPipeline *pipeline)
1327 {
1328 ShellRecorder *recorder = pipeline->recorder;
1329 if (!recorder)
1330 return;
1331
1332 if (recorder->update_memory_used_timeout == 0)
1333 recorder->update_memory_used_timeout = g_timeout_add (UPDATE_MEMORY_USED_DELAY,
1334 recorder_update_memory_used_timeout,
1335 recorder);
1336 }
1337
1338 static void
1339 recorder_pipeline_free (RecorderPipeline *pipeline)
1340 {
1341 if (pipeline->pipeline != NULL)
1342 gst_object_unref (pipeline->pipeline);
1343
1344 if (pipeline->outfile != -1)
1345 close (pipeline->outfile);
1346
1347 g_free (pipeline);
1348 }
1349
1350 /* Function gets called on pipeline-global events; we use it to
1351 * know when the pipeline is finished.
1352 */
1353 static gboolean
1354 recorder_pipeline_bus_watch (GstBus *bus,
1355 GstMessage *message,
1356 gpointer data)
1357 {
1358 RecorderPipeline *pipeline = data;
1359
1360 switch (message->type)
1361 {
1362 case GST_MESSAGE_EOS:
1363 recorder_pipeline_closed (pipeline);
1364 return FALSE; /* remove watch */
1365 case GST_MESSAGE_ERROR:
1366 {
1367 GError *error;
1368
1369 gst_message_parse_error (message, &error, NULL);
1370 g_warning ("Error in recording pipeline: %s\n", error->message);
1371 g_error_free (error);
1372 recorder_pipeline_closed (pipeline);
1373 return FALSE; /* remove watch */
1374 }
1375 default:
1376 break;
1377 }
1378
1379 /* Leave the watch in place */
1380 return TRUE;
1381 }
1382
1383 /* Clean up when the pipeline is finished
1384 */
1385 static void
1386 recorder_pipeline_closed (RecorderPipeline *pipeline)
1387 {
1388 g_signal_handlers_disconnect_by_func (pipeline->src,
1389 (gpointer) recorder_pipeline_on_memory_used_changed,
1390 pipeline);
1391
1392 gst_element_set_state (pipeline->pipeline, GST_STATE_NULL);
1393
1394 if (pipeline->recorder)
1395 {
1396 ShellRecorder *recorder = pipeline->recorder;
1397 if (pipeline == recorder->current_pipeline)
1398 {
1399 /* Error case; force a close */
1400 recorder->current_pipeline = NULL;
1401 shell_recorder_close (recorder);
1402 }
1403
1404 recorder->pipelines = g_slist_remove (recorder->pipelines, pipeline);
1405 }
1406
1407 recorder_pipeline_free (pipeline);
1408 }
1409
1410 /*
1411 * Replaces '%T' in the passed pipeline with the thread count,
1412 * the maximum possible value is 64 (limit of what vp8enc supports)
1413 *
1414 * It is assumes that %T occurs only once.
1415 */
1416 static char*
1417 substitute_thread_count (const char *pipeline)
1418 {
1419 char *tmp;
1420 int n_threads;
1421 GString *result;
1422
1423 tmp = strstr (pipeline, "%T");
1424
1425 if (!tmp)
1426 return g_strdup (pipeline);
1427
1428 #ifdef _SC_NPROCESSORS_ONLN
1429 {
1430 int n_processors = sysconf (_SC_NPROCESSORS_ONLN); /* includes hyper-threading */
1431 n_threads = MIN (MAX (1, n_processors - 1), 64);
1432 }
1433 #else
1434 n_threads = 3;
1435 #endif
1436
1437 result = g_string_new (NULL);
1438 g_string_append_len (result, pipeline, tmp - pipeline);
1439 g_string_append_printf (result, "%d", n_threads);
1440 g_string_append (result, tmp + 2);
1441
1442 return g_string_free (result, FALSE);;
1443 }
1444
1445 static gboolean
1446 recorder_open_pipeline (ShellRecorder *recorder)
1447 {
1448 RecorderPipeline *pipeline;
1449 const char *pipeline_description;
1450 char *parsed_pipeline;
1451 GError *error = NULL;
1452 GstBus *bus;
1453
1454 pipeline = g_new0(RecorderPipeline, 1);
1455 pipeline->recorder = recorder;
1456 pipeline->outfile = - 1;
1457
1458 pipeline_description = recorder->pipeline_description;
1459 if (!pipeline_description)
1460 pipeline_description = DEFAULT_PIPELINE;
1461
1462 parsed_pipeline = substitute_thread_count (pipeline_description);
1463
1464 pipeline->pipeline = gst_parse_launch_full (parsed_pipeline, NULL,
1465 GST_PARSE_FLAG_FATAL_ERRORS,
1466 &error);
1467 g_free (parsed_pipeline);
1468
1469 if (pipeline->pipeline == NULL)
1470 {
1471 g_warning ("ShellRecorder: failed to parse pipeline: %s", error->message);
1472 g_error_free (error);
1473 goto error;
1474 }
1475
1476 if (!recorder_pipeline_add_source (pipeline))
1477 goto error;
1478
1479 if (!recorder_pipeline_add_sink (pipeline))
1480 goto error;
1481
1482 gst_element_set_state (pipeline->pipeline, GST_STATE_PLAYING);
1483
1484 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline->pipeline));
1485 gst_bus_add_watch (bus, recorder_pipeline_bus_watch, pipeline);
1486 gst_object_unref (bus);
1487
1488 g_signal_connect (pipeline->src, "notify::memory-used",
1489 G_CALLBACK (recorder_pipeline_on_memory_used_changed), pipeline);
1490
1491 recorder->current_pipeline = pipeline;
1492 recorder->pipelines = g_slist_prepend (recorder->pipelines, pipeline);
1493
1494 return TRUE;
1495
1496 error:
1497 recorder_pipeline_free (pipeline);
1498
1499 return FALSE;
1500 }
1501
1502 static void
1503 recorder_close_pipeline (ShellRecorder *recorder)
1504 {
1505 if (recorder->current_pipeline != NULL)
1506 {
1507 /* This will send an EOS (end-of-stream) message after the last frame
1508 * is written. The bus watch for the pipeline will get it and do
1509 * final cleanup
1510 */
1511 shell_recorder_src_close (SHELL_RECORDER_SRC (recorder->current_pipeline->src));
1512
1513 recorder->current_pipeline = NULL;
1514 }
1515 }
1516
1517 /**
1518 * shell_recorder_new:
1519 * @stage: The #ClutterStage
1520 *
1521 * Create a new #ShellRecorder to record movies of a #ClutterStage
1522 *
1523 * Return value: The newly created #ShellRecorder object
1524 */
1525 ShellRecorder *
1526 shell_recorder_new (ClutterStage *stage)
1527 {
1528 return g_object_new (SHELL_TYPE_RECORDER,
1529 "stage", stage,
1530 NULL);
1531 }
1532
1533 /**
1534 * shell_recorder_set_framerate:
1535 * @recorder: the #ShellRecorder
1536 * @framerate: Framerate used for resulting video in frames-per-second.
1537 *
1538 * Sets the number of frames per second we try to record. Less frames
1539 * will be recorded when the screen doesn't need to be redrawn this
1540 * quickly. (This value will also be set as the framerate for the
1541 * GStreamer pipeline; whether that has an effect on the resulting
1542 * video will depend on the details of the pipeline and the codec. The
1543 * default encoding to webm format doesn't pay attention to the pipeline
1544 * framerate.)
1545 *
1546 * The default value is 30.
1547 */
1548 void
1549 shell_recorder_set_framerate (ShellRecorder *recorder,
1550 int framerate)
1551 {
1552 g_return_if_fail (SHELL_IS_RECORDER (recorder));
1553
1554 recorder_set_framerate (recorder, framerate);
1555 }
1556
1557 /**
1558 * shell_recorder_set_filename:
1559 * @recorder: the #ShellRecorder
1560 * @filename: the filename template to use for output files,
1561 * or %NULL for the defalt value.
1562 *
1563 * Sets the filename that will be used when creating output
1564 * files. This is only used if the configured pipeline has an
1565 * unconnected source pad (as the default pipeline does). If
1566 * the pipeline is complete, then the filename is unused. The
1567 * provided string is used as a template.It can contain
1568 * the following escapes:
1569 *
1570 * %d: The current date as YYYYYMMDD
1571 * %%: A literal percent
1572 *
1573 * The default value is 'shell-%d%u-%c.ogg'.
1574 */
1575 void
1576 shell_recorder_set_filename (ShellRecorder *recorder,
1577 const char *filename)
1578 {
1579 g_return_if_fail (SHELL_IS_RECORDER (recorder));
1580
1581 recorder_set_filename (recorder, filename);
1582
1583 }
1584
1585 /**
1586 * shell_recorder_set_pipeline:
1587 * @recorder: the #ShellRecorder
1588 * @pipeline: (allow-none): the GStreamer pipeline used to encode recordings
1589 * or %NULL for the default value.
1590 *
1591 * Sets the GStreamer pipeline used to encode recordings.
1592 * It follows the syntax used for gst-launch. The pipeline
1593 * should have an unconnected sink pad where the recorded
1594 * video is recorded. It will normally have a unconnected
1595 * source pad; output from that pad will be written into the
1596 * output file. (See shell_recorder_set_filename().) However
1597 * the pipeline can also take care of its own output - this
1598 * might be used to send the output to an icecast server
1599 * via shout2send or similar.
1600 *
1601 * The default value is 'vp8enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmux'
1602 */
1603 void
1604 shell_recorder_set_pipeline (ShellRecorder *recorder,
1605 const char *pipeline)
1606 {
1607 g_return_if_fail (SHELL_IS_RECORDER (recorder));
1608
1609 recorder_set_pipeline (recorder, pipeline);
1610 }
1611
1612 /**
1613 * shell_recorder_record:
1614 * @recorder: the #ShellRecorder
1615 *
1616 * Starts recording, Starting the recording may fail if the output file
1617 * cannot be opened, or if the output stream cannot be created
1618 * for other reasons. In that case a warning is printed to
1619 * stderr. There is no way currently to get details on how
1620 * recording failed to start.
1621 *
1622 * An extra reference count is added to the recorder if recording
1623 * is succesfully started; the recording object will not be freed
1624 * until recording is stopped even if the creator no longer holds
1625 * a reference. Recording is automatically stopped if the stage
1626 * is destroyed.
1627 *
1628 * Return value: %TRUE if recording was succesfully started
1629 */
1630 gboolean
1631 shell_recorder_record (ShellRecorder *recorder)
1632 {
1633 g_return_val_if_fail (SHELL_IS_RECORDER (recorder), FALSE);
1634 g_return_val_if_fail (recorder->stage != NULL, FALSE);
1635 g_return_val_if_fail (recorder->state != RECORDER_STATE_RECORDING, FALSE);
1636
1637 if (!recorder_open_pipeline (recorder))
1638 return FALSE;
1639
1640 recorder->start_time = get_wall_time();
1641 recorder->last_frame_time = 0;
1642
1643 recorder->state = RECORDER_STATE_RECORDING;
1644 recorder_add_update_pointer_timeout (recorder);
1645
1646 /* Set up repaint hook */
1647 recorder->repaint_hook_id = clutter_threads_add_repaint_func(recorder_repaint_hook, recorder->stage, NULL);
1648
1649 /* Record an initial frame and also redraw with the indicator */
1650 clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
1651
1652 /* We keep a ref while recording to let a caller start a recording then
1653 * drop their reference to the recorder
1654 */
1655 g_object_ref (recorder);
1656
1657 return TRUE;
1658 }
1659
1660 /**
1661 * shell_recorder_close:
1662 * @recorder: the #ShellRecorder
1663 *
1664 * Stops recording. It's possible to call shell_recorder_record()
1665 * again to reopen a new recording stream, but unless change the
1666 * recording filename, this may result in the old recording being
1667 * overwritten.
1668 */
1669 void
1670 shell_recorder_close (ShellRecorder *recorder)
1671 {
1672 g_return_if_fail (SHELL_IS_RECORDER (recorder));
1673 g_return_if_fail (recorder->state != RECORDER_STATE_CLOSED);
1674
1675 /* We want to record one more frame since some time may have
1676 * elapsed since the last frame
1677 */
1678 clutter_actor_paint (CLUTTER_ACTOR (recorder->stage));
1679
1680 recorder_remove_update_pointer_timeout (recorder);
1681 recorder_close_pipeline (recorder);
1682
1683 /* Queue a redraw to remove the recording indicator */
1684 clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
1685
1686 if (recorder->repaint_hook_id != 0)
1687 {
1688 clutter_threads_remove_repaint_func (recorder->repaint_hook_id);
1689 recorder->repaint_hook_id = 0;
1690 }
1691
1692 recorder_remove_redraw_timeout (recorder);
1693 recorder_close_pipeline (recorder);
1694
1695 recorder->state = RECORDER_STATE_CLOSED;
1696
1697 /* Release the refcount we took when we started recording */
1698 g_object_unref (recorder);
1699 }
1700
1701 /**
1702 * shell_recorder_is_recording:
1703 *
1704 * Determine if recording is currently in progress. (The recorder
1705 * is not paused or closed.)
1706 *
1707 * Return value: %TRUE if the recorder is currently recording.
1708 */
1709 gboolean
1710 shell_recorder_is_recording (ShellRecorder *recorder)
1711 {
1712 g_return_val_if_fail (SHELL_IS_RECORDER (recorder), FALSE);
1713
1714 return recorder->state == RECORDER_STATE_RECORDING;
1715 }