~ubuntu-branches/debian/jessie/gnome-shell/jessie

« back to all changes in this revision

Viewing changes to .pc/00git-ShellRecorder-Use-cogl_read_pixels.patch/src/shell-recorder.c

  • Committer: Package Import Robot
  • Author(s): Michael Biebl, Michael Biebl, Rico Tzschichholz
  • Date: 2011-10-14 17:22:55 UTC
  • Revision ID: package-import@ubuntu.com-20111014172255-z2imjapg2uy8m4vd
Tags: 3.0.2-5
[ Michael Biebl ]
* debian/rules:
  - Make network-manager and gnome-bluetooth (build) dependencies linux-any.

[ Rico Tzschichholz ]
* Add 00git-ShellRecorder-Use-cogl_read_pixels.patch to fix FTBFS on arm

Show diffs side-by-side

added added

removed removed

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