1
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
13
#include "shell-recorder-src.h"
14
#include "shell-recorder.h"
16
#include <clutter/x11/clutter-x11.h>
17
#include <X11/extensions/Xfixes.h>
20
RECORDER_STATE_CLOSED,
21
RECORDER_STATE_PAUSED,
22
RECORDER_STATE_RECORDING
25
typedef struct _RecorderPipeline RecorderPipeline;
27
struct _ShellRecorderClass
29
GObjectClass parent_class;
32
struct _ShellRecorder {
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)
40
guint memory_used; /* Current memory used. (In kB) */
43
char *unique; /* The unique string we are using for this recording */
44
int count; /* How many times the recording has been started */
50
gboolean have_pointer;
55
int xfixes_event_base;
57
CoglHandle *recording_icon; /* icon shown while playing */
59
cairo_surface_t *cursor_image;
63
gboolean only_paint; /* Used to temporarily suppress recording */
65
gboolean have_pack_invert; /* True when GL_MESA_pack_invert is available */
68
char *pipeline_description;
70
gboolean filename_has_count; /* %c used: handle pausing differently */
72
/* We might have multiple pipelines that are finishing encoding
73
* to go along with the current pipeline where we are recording.
75
RecorderPipeline *current_pipeline; /* current pipeline */
76
GSList *pipelines; /* all pipelines */
78
GstClockTime start_time; /* When we started recording (adjusted for pauses) */
79
GstClockTime pause_time; /* When the pipeline was paused */
81
/* GSource IDs for different timeouts and idles */
84
guint update_memory_used_timeout;
85
guint update_pointer_timeout;
86
guint repaint_hook_id;
89
struct _RecorderPipeline
91
ShellRecorder *recorder;
97
static void recorder_set_stage (ShellRecorder *recorder,
99
static void recorder_set_framerate (ShellRecorder *recorder,
101
static void recorder_set_pipeline (ShellRecorder *recorder,
102
const char *pipeline);
103
static void recorder_set_filename (ShellRecorder *recorder,
104
const char *filename);
106
static void recorder_pipeline_set_caps (RecorderPipeline *pipeline);
107
static void recorder_pipeline_closed (RecorderPipeline *pipeline);
117
G_DEFINE_TYPE(ShellRecorder, shell_recorder, G_TYPE_OBJECT);
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
126
#define DEFAULT_FRAMES_PER_SECOND 15
128
/* The time (in milliseconds) between querying the server for the cursor
131
#define UPDATE_POINTER_TIME 100
133
/* The time we wait (in milliseconds) before redrawing when the memory used
136
#define UPDATE_MEMORY_USED_DELAY 500
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.
143
#define MAXIMUM_PAUSE_TIME 1000
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.)
150
#define DEFAULT_PIPELINE "videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux"
152
/* The default filename pattern. Example shell-20090311b-2.webm
154
#define DEFAULT_FILENAME "shell-%d%u-%c.webm"
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.
159
#define DEFAULT_MEMORY_TARGET (512*1024)
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.
166
create_recording_icon (void)
168
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 32, 32);
170
cairo_pattern_t *pat;
173
cr = cairo_create (surface);
175
/* clear to transparent */
177
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
182
pat = cairo_pattern_create_radial (16, 16, 6,
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 */
189
cairo_set_source (cr, pat);
191
cairo_pattern_destroy (pat);
194
cairo_arc (cr, 16, 16, 8,
196
cairo_set_source_rgb (cr, 1, 0, 0);
201
texture = cogl_texture_new_from_data (32, 32,
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);
213
get_memory_target (void)
217
/* Really simple "get amount of memory on the machine" if it
218
* doesn't work, you just get the default memory target.
220
f = fopen("/proc/meminfo", "r");
222
return DEFAULT_MEMORY_TARGET;
226
gchar line_buffer[1024];
228
if (fscanf(f, "MemTotal: %u", &mem_total) == 1)
231
return mem_total / 2;
233
/* Skip to the next line and discard what we read */
234
if (fgets(line_buffer, sizeof(line_buffer), f) == NULL)
240
return DEFAULT_MEMORY_TARGET;
244
* Used to force full stage redraws during recording to avoid artifacts
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
251
recorder_repaint_hook (gpointer data)
253
ClutterActor *stage = data;
254
clutter_actor_queue_redraw (stage);
260
shell_recorder_init (ShellRecorder *recorder)
262
/* Calling gst_init() is a no-op if GStreamer was previously initialized */
263
gst_init (NULL, NULL);
265
shell_recorder_src_register ();
267
recorder->recording_icon = create_recording_icon ();
268
recorder->memory_target = get_memory_target();
270
recorder->state = RECORDER_STATE_CLOSED;
271
recorder->framerate = DEFAULT_FRAMES_PER_SECOND;
275
shell_recorder_finalize (GObject *object)
277
ShellRecorder *recorder = SHELL_RECORDER (object);
280
for (l = recorder->pipelines; l; l = l->next)
282
RecorderPipeline *pipeline = l->data;
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.)
288
pipeline->recorder = NULL;
291
if (recorder->update_memory_used_timeout)
292
g_source_remove (recorder->update_memory_used_timeout);
294
if (recorder->cursor_image)
295
cairo_surface_destroy (recorder->cursor_image);
297
recorder_set_stage (recorder, NULL);
298
recorder_set_pipeline (recorder, NULL);
299
recorder_set_filename (recorder, NULL);
301
cogl_handle_unref (recorder->recording_icon);
303
G_OBJECT_CLASS (shell_recorder_parent_class)->finalize (object);
307
recorder_on_stage_destroy (ClutterActor *actor,
308
ShellRecorder *recorder)
310
recorder_set_stage (recorder, NULL);
313
/* Add together the memory used by all pipelines; both the
314
* currently recording pipeline and pipelines finishing
315
* recording asynchronously.
318
recorder_update_memory_used (ShellRecorder *recorder,
321
guint memory_used = 0;
324
for (l = recorder->pipelines; l; l = l->next)
326
RecorderPipeline *pipeline = l->data;
327
guint pipeline_memory_used;
329
g_object_get (pipeline->src,
330
"memory-used", &pipeline_memory_used,
332
memory_used += pipeline_memory_used;
335
if (memory_used != recorder->memory_used)
337
recorder->memory_used = memory_used;
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.
345
recorder->only_paint = TRUE;
346
clutter_redraw (recorder->stage);
347
recorder->only_paint = FALSE;
352
/* Timeout used to avoid not drawing for more than MAXIMUM_PAUSE_TIME
355
recorder_redraw_timeout (gpointer data)
357
ShellRecorder *recorder = data;
359
recorder->redraw_timeout = 0;
360
clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
366
recorder_add_redraw_timeout (ShellRecorder *recorder)
368
if (recorder->redraw_timeout == 0)
370
recorder->redraw_timeout = g_timeout_add (MAXIMUM_PAUSE_TIME,
371
recorder_redraw_timeout,
377
recorder_remove_redraw_timeout (ShellRecorder *recorder)
379
if (recorder->redraw_timeout != 0)
381
g_source_remove (recorder->redraw_timeout);
382
recorder->redraw_timeout = 0;
387
recorder_fetch_cursor_image (ShellRecorder *recorder)
389
XFixesCursorImage *cursor_image;
394
if (!recorder->have_xfixes)
397
cursor_image = XFixesGetCursorImage (clutter_x11_get_default_display ());
399
recorder->cursor_hot_x = cursor_image->xhot;
400
recorder->cursor_hot_y = cursor_image->yhot;
402
recorder->cursor_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
404
cursor_image->height);
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,
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];
416
cairo_surface_mark_dirty (recorder->cursor_image);
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.
426
recorder_draw_cursor (ShellRecorder *recorder,
429
cairo_surface_t *surface;
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)
441
if (!recorder->cursor_image)
442
recorder_fetch_cursor_image (recorder);
444
if (!recorder->cursor_image)
447
surface = cairo_image_surface_create_for_data (GST_BUFFER_DATA(buffer),
449
recorder->stage_width,
450
recorder->stage_height,
451
recorder->stage_width * 4);
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)
458
cairo_translate(cr, 0, recorder->stage_height);
459
cairo_scale(cr, 1, -1);
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);
469
cairo_surface_destroy (surface);
472
/* Draw an overlay indicating how much of the target memory is used
473
* for buffering frames.
476
recorder_draw_buffer_meter (ShellRecorder *recorder)
480
recorder_update_memory_used (recorder, FALSE);
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);
488
cogl_set_source_color4f (0, 1, 0, 1);
490
fill_level = MIN (60, (recorder->memory_used * 60) / recorder->memory_target);
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);
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?
514
g_get_current_time (&tv);
516
return tv.tv_sec * 1000000000LL + tv.tv_usec * 1000LL;
519
/* Retrieve a frame and feed it into the pipeline
522
recorder_record_frame (ShellRecorder *recorder)
528
size = recorder->stage_width * recorder->stage_height * 4;
529
data = g_malloc (size);
531
buffer = gst_buffer_new();
532
GST_BUFFER_SIZE(buffer) = size;
533
GST_BUFFER_MALLOCDATA(buffer) = GST_BUFFER_DATA(buffer) = data;
535
GST_BUFFER_TIMESTAMP(buffer) = get_wall_time() - recorder->start_time;
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)
542
* http://bugzilla.openedhand.com/show_bug.cgi?id=1959
545
/* Flush any primitives that Cogl has batched up */
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);
555
if (recorder->have_pack_invert)
556
glPixelStorei (GL_PACK_INVERT_MESA, TRUE);
558
glReadBuffer (GL_BACK_LEFT);
560
recorder->stage_width, recorder->stage_height,
562
GL_UNSIGNED_INT_8_8_8_8_REV,
565
if (recorder->have_pack_invert)
566
glPixelStorei (GL_PACK_INVERT_MESA, FALSE);
568
recorder_draw_cursor (recorder, buffer);
570
shell_recorder_src_add_buffer (SHELL_RECORDER_SRC (recorder->current_pipeline->src), buffer);
571
gst_buffer_unref (buffer);
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);
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.
582
recorder_on_stage_paint (ClutterActor *actor,
583
ShellRecorder *recorder)
585
if (recorder->state == RECORDER_STATE_RECORDING)
587
if (!recorder->only_paint)
588
recorder_record_frame (recorder);
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);
595
if (recorder->state == RECORDER_STATE_RECORDING || recorder->memory_used != 0)
596
recorder_draw_buffer_meter (recorder);
600
recorder_update_size (ShellRecorder *recorder)
602
ClutterActorBox allocation;
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);
610
recorder_on_stage_notify_size (GObject *object,
612
ShellRecorder *recorder)
614
recorder_update_size (recorder);
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.
620
if (recorder->current_pipeline)
621
recorder_pipeline_set_caps (recorder->current_pipeline);
625
recorder_idle_redraw (gpointer data)
627
ShellRecorder *recorder = data;
629
recorder->redraw_idle = 0;
630
clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
636
recorder_queue_redraw (ShellRecorder *recorder)
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
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);
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
653
static ClutterX11FilterReturn
654
recorder_event_filter (XEvent *xev,
658
ShellRecorder *recorder = data;
660
if (xev->xany.window != clutter_x11_get_stage_window (recorder->stage))
661
return CLUTTER_X11_FILTER_CONTINUE;
663
if (xev->xany.type == recorder->xfixes_event_base + XFixesCursorNotify)
665
XFixesCursorNotifyEvent *notify_event = (XFixesCursorNotifyEvent *)xev;
667
if (notify_event->subtype == XFixesDisplayCursorNotify)
669
if (recorder->cursor_image)
671
cairo_surface_destroy (recorder->cursor_image);
672
recorder->cursor_image = NULL;
675
recorder_queue_redraw (recorder);
678
else if (xev->xany.type == MotionNotify)
680
recorder->pointer_x = xev->xmotion.x;
681
recorder->pointer_y = xev->xmotion.y;
683
recorder_queue_redraw (recorder);
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.
689
else if (xev->xany.type == EnterNotify &&
690
(xev->xcrossing.detail != NotifyVirtual &&
691
xev->xcrossing.detail != NotifyNonlinearVirtual))
693
recorder->have_pointer = TRUE;
694
recorder->pointer_x = xev->xcrossing.x;
695
recorder->pointer_y = xev->xcrossing.y;
697
recorder_queue_redraw (recorder);
699
else if (xev->xany.type == LeaveNotify &&
700
(xev->xcrossing.detail != NotifyVirtual &&
701
xev->xcrossing.detail != NotifyNonlinearVirtual))
703
recorder->have_pointer = FALSE;
704
recorder->pointer_x = xev->xcrossing.x;
705
recorder->pointer_y = xev->xcrossing.y;
707
recorder_queue_redraw (recorder);
710
return CLUTTER_X11_FILTER_CONTINUE;
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.
719
recorder_get_initial_cursor_position (ShellRecorder *recorder)
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;
728
int window_x, window_y;
731
XGrabServer(xdisplay);
733
XGetWindowAttributes (xdisplay, xwindow, &xwa);
734
XQueryTree (xdisplay, xwindow, &root, &parent, &children, &n_children);
737
if (xwa.map_state == IsViewable &&
738
XQueryPointer (xdisplay, parent,
739
&root, &child, &root_x, &root_y, &window_x, &window_y, &mask) &&
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.
747
XTranslateCoordinates(xdisplay, parent, xwindow,
749
&window_x, &window_y, &child);
752
recorder->have_pointer = TRUE;
753
recorder->pointer_x = window_x;
754
recorder->pointer_y = window_y;
758
recorder->have_pointer = FALSE;
760
XUngrabServer(xdisplay);
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.
768
XSelectInput(xdisplay, xwindow,
769
xwa.your_event_mask | EnterWindowMask | LeaveWindowMask | PointerMotionMask);
772
/* When the cursor is not over the stage's input area, we query for the
773
* pointer position in a timeout.
776
recorder_update_pointer (ShellRecorder *recorder)
778
Display *xdisplay = clutter_x11_get_default_display ();
779
Window xwindow = clutter_x11_get_stage_window (recorder->stage);
782
int window_x, window_y;
785
if (recorder->have_pointer)
788
if (XQueryPointer (xdisplay, xwindow,
789
&root, &child, &root_x, &root_y, &window_x, &window_y, &mask))
791
if (window_x != recorder->pointer_x || window_y != recorder->pointer_y)
793
recorder->pointer_x = window_x;
794
recorder->pointer_y = window_y;
796
recorder_queue_redraw (recorder);
802
recorder_update_pointer_timeout (gpointer data)
804
recorder_update_pointer (data);
810
recorder_add_update_pointer_timeout (ShellRecorder *recorder)
812
if (!recorder->update_pointer_timeout)
813
recorder->update_pointer_timeout = g_timeout_add (UPDATE_POINTER_TIME,
814
recorder_update_pointer_timeout,
819
recorder_remove_update_pointer_timeout (ShellRecorder *recorder)
821
if (recorder->update_pointer_timeout)
823
g_source_remove (recorder->update_pointer_timeout);
824
recorder->update_pointer_timeout = 0;
829
recorder_set_stage (ShellRecorder *recorder,
832
if (recorder->stage == stage)
835
if (recorder->current_pipeline)
836
shell_recorder_close (recorder);
840
g_signal_handlers_disconnect_by_func (recorder->stage,
841
(void *)recorder_on_stage_destroy,
843
g_signal_handlers_disconnect_by_func (recorder->stage,
844
(void *)recorder_on_stage_paint,
846
g_signal_handlers_disconnect_by_func (recorder->stage,
847
(void *)recorder_on_stage_notify_size,
850
clutter_x11_remove_filter (recorder_event_filter, recorder);
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.
857
if (recorder->redraw_idle)
859
g_source_remove (recorder->redraw_idle);
860
recorder->redraw_idle = 0;
864
recorder->stage = stage;
869
const char *gl_extensions;
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);
881
clutter_x11_add_filter (recorder_event_filter, recorder);
883
recorder_update_size (recorder);
885
recorder->have_xfixes = XFixesQueryExtension (clutter_x11_get_default_display (),
886
&recorder->xfixes_event_base,
888
if (recorder->have_xfixes)
889
XFixesSelectCursorInput (clutter_x11_get_default_display (),
890
clutter_x11_get_stage_window (stage),
891
XFixesDisplayCursorNotifyMask);
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;
897
recorder_get_initial_cursor_position (recorder);
902
recorder_set_framerate (ShellRecorder *recorder,
905
if (framerate == recorder->framerate)
908
if (recorder->current_pipeline)
909
shell_recorder_close (recorder);
911
recorder->framerate = framerate;
913
g_object_notify (G_OBJECT (recorder), "framerate");
917
recorder_set_pipeline (ShellRecorder *recorder,
918
const char *pipeline)
920
if (pipeline == recorder->pipeline_description ||
921
(pipeline && recorder->pipeline_description && strcmp (recorder->pipeline_description, pipeline) == 0))
924
if (recorder->current_pipeline)
925
shell_recorder_close (recorder);
927
if (recorder->pipeline_description)
928
g_free (recorder->pipeline_description);
930
recorder->pipeline_description = g_strdup (pipeline);
932
g_object_notify (G_OBJECT (recorder), "pipeline");
936
recorder_set_filename (ShellRecorder *recorder,
937
const char *filename)
939
if (filename == recorder->filename ||
940
(filename && recorder->filename && strcmp (recorder->filename, filename) == 0))
943
if (recorder->current_pipeline)
944
shell_recorder_close (recorder);
946
if (recorder->filename)
947
g_free (recorder->filename);
949
recorder->filename = g_strdup (filename);
951
g_object_notify (G_OBJECT (recorder), "filename");
955
shell_recorder_set_property (GObject *object,
960
ShellRecorder *recorder = SHELL_RECORDER (object);
965
recorder_set_stage (recorder, g_value_get_object (value));
968
recorder_set_framerate (recorder, g_value_get_int (value));
971
recorder_set_pipeline (recorder, g_value_get_string (value));
974
recorder_set_filename (recorder, g_value_get_string (value));
977
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
983
shell_recorder_get_property (GObject *object,
988
ShellRecorder *recorder = SHELL_RECORDER (object);
993
g_value_set_object (value, G_OBJECT (recorder->stage));
996
g_value_set_int (value, recorder->framerate);
999
g_value_set_string (value, recorder->pipeline_description);
1002
g_value_set_string (value, recorder->filename);
1005
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1011
shell_recorder_class_init (ShellRecorderClass *klass)
1013
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
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;
1019
g_object_class_install_property (gobject_class,
1021
g_param_spec_object ("stage",
1025
G_PARAM_READWRITE));
1027
g_object_class_install_property (gobject_class,
1029
g_param_spec_int ("framerate",
1031
"Framerate used for resulting video in frames-per-second",
1034
DEFAULT_FRAMES_PER_SECOND,
1035
G_PARAM_READWRITE));
1037
g_object_class_install_property (gobject_class,
1039
g_param_spec_string ("pipeline",
1041
"GStreamer pipeline description to encode recordings",
1043
G_PARAM_READWRITE));
1045
g_object_class_install_property (gobject_class,
1047
g_param_spec_string ("filename",
1049
"The filename template to use for output files",
1051
G_PARAM_READWRITE));
1054
/* Sets the GstCaps (video format, in this case) on the stream
1057
recorder_pipeline_set_caps (RecorderPipeline *pipeline)
1061
/* The data is always native-endian xRGB; ffmpegcolorspace
1062
* doesn't support little-endian xRGB, but does support
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,
1073
"red_mask", G_TYPE_INT, 0xff0000,
1074
"green_mask", G_TYPE_INT, 0x00ff00,
1075
"blue_mask", G_TYPE_INT, 0x0000ff,
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,
1082
g_object_set (pipeline->src, "caps", caps, NULL);
1083
gst_caps_unref (caps);
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.
1091
recorder_pipeline_add_source (RecorderPipeline *pipeline)
1093
GstPad *sink_pad = NULL, *src_pad = NULL;
1094
gboolean result = FALSE;
1095
GstElement *ffmpegcolorspace;
1096
GstElement *videoflip;
1097
GError *error = NULL;
1099
sink_pad = gst_bin_find_unlinked_pad (GST_BIN (pipeline->pipeline), GST_PAD_SINK);
1100
if (sink_pad == NULL)
1102
g_warning("ShellRecorder: pipeline has no unlinked sink pad");
1106
pipeline->src = gst_element_factory_make ("shellrecordersrc", NULL);
1107
if (pipeline->src == NULL)
1109
g_warning ("Can't create recorder source element");
1112
gst_bin_add (GST_BIN (pipeline->pipeline), pipeline->src);
1114
recorder_pipeline_set_caps (pipeline);
1116
/* The ffmpegcolorspace element is a generic converter; it will convert
1117
* our supplied fixed format data into whatever the encoder wants
1119
ffmpegcolorspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
1120
if (!ffmpegcolorspace)
1122
g_warning("Can't create ffmpegcolorspace element");
1125
gst_bin_add (GST_BIN (pipeline->pipeline), ffmpegcolorspace);
1127
/* glReadPixels gives us an upside-down buffer, so we have to flip it back
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.
1134
* We use gst_parse_launch to avoid having to know the enum value for flip-vertical
1137
if (!pipeline->recorder->have_pack_invert)
1139
videoflip = gst_parse_launch_full ("videoflip method=vertical-flip", NULL,
1140
GST_PARSE_FLAG_FATAL_ERRORS,
1142
if (videoflip == NULL)
1144
g_warning("Can't create videoflip element: %s", error->message);
1145
g_error_free (error);
1149
gst_bin_add (GST_BIN (pipeline->pipeline), videoflip);
1150
gst_element_link_many (pipeline->src, ffmpegcolorspace, videoflip, NULL);
1152
src_pad = gst_element_get_static_pad (videoflip, "src");
1156
gst_element_link_many (pipeline->src, ffmpegcolorspace, NULL);
1157
src_pad = gst_element_get_static_pad (ffmpegcolorspace, "src");
1162
g_warning("ShellRecorder: can't get src pad to link into pipeline");
1166
if (gst_pad_link (src_pad, sink_pad) != GST_PAD_LINK_OK)
1168
g_warning("ShellRecorder: can't link to sink pad");
1176
gst_object_unref (sink_pad);
1178
gst_object_unref (src_pad);
1183
/* Counts '', 'a', ..., 'z', 'aa', ..., 'az', 'ba', ... */
1185
increment_unique (GString *unique)
1189
for (i = unique->len - 1; i >= 0; i--)
1191
if (unique->str[i] != 'z')
1197
unique->str[i] = 'a';
1200
g_string_prepend_c (unique, 'a');
1204
get_absolute_path (char *maybe_relative)
1208
if (g_path_is_absolute (maybe_relative))
1209
path = g_strdup (maybe_relative);
1212
char *cwd = g_get_current_dir ();
1213
path = g_build_filename (cwd, maybe_relative, NULL);
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
1226
recorder_open_outfile (ShellRecorder *recorder)
1228
GString *unique = g_string_new (NULL); /* add to filename to make it unique */
1229
const char *pattern;
1235
pattern = recorder->filename;
1237
pattern = DEFAULT_FILENAME;
1241
GString *filename = g_string_new (NULL);
1244
for (p = pattern; *p; p++)
1252
g_string_append_c (filename, '%');
1256
/* Count distinguishing multiple files created in session */
1257
g_string_append_printf (filename, "%d", recorder->count);
1258
recorder->filename_has_count = TRUE;
1263
/* Appends date as YYYYMMDD */
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));
1276
if (recorder->unique)
1277
g_string_append (filename, recorder->unique);
1279
g_string_append (filename, unique->str);
1282
g_warning ("Unknown escape %%%c in filename", *p);
1289
g_string_append_c (filename, *p);
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.
1296
flags = O_WRONLY | O_CREAT | O_TRUNC;
1297
if (recorder->filename_has_count)
1300
outfile = open (filename->str, flags, 0666);
1303
char *path = get_absolute_path (filename->str);
1304
g_printerr ("Recording to %s\n", path);
1307
g_string_free (filename, TRUE);
1311
if (outfile == -1 &&
1312
(errno != EEXIST || !recorder->filename_has_count))
1314
g_warning ("Cannot open output file '%s': %s", filename->str, g_strerror (errno));
1315
g_string_free (filename, TRUE);
1319
if (recorder->unique)
1321
/* We've already picked a unique string based on count=1, and now we had a collision
1322
* for a subsequent count.
1324
g_warning ("Name collision with existing file for '%s'", filename->str);
1325
g_string_free (filename, TRUE);
1329
g_string_free (filename, TRUE);
1331
increment_unique (unique);
1336
recorder->unique = g_string_free (unique, FALSE);
1338
g_string_free (unique, TRUE);
1343
/* Augments the supplied pipeline with a sink element to write to the output
1344
* file, if necessary.
1347
recorder_pipeline_add_sink (RecorderPipeline *pipeline)
1349
GstPad *sink_pad = NULL, *src_pad = NULL;
1351
gboolean result = FALSE;
1353
src_pad = gst_bin_find_unlinked_pad (GST_BIN (pipeline->pipeline), GST_PAD_SRC);
1354
if (src_pad == NULL)
1356
/* Nothing to do - assume that we were given a complete pipeline */
1360
pipeline->outfile = recorder_open_outfile (pipeline->recorder);
1361
if (pipeline->outfile == -1)
1364
fdsink = gst_element_factory_make ("fdsink", NULL);
1367
g_warning("Can't create fdsink element");
1370
gst_bin_add (GST_BIN (pipeline->pipeline), fdsink);
1371
g_object_set (fdsink, "fd", pipeline->outfile, NULL);
1373
sink_pad = gst_element_get_static_pad (fdsink, "sink");
1376
g_warning("ShellRecorder: can't get sink pad to link pipeline output");
1380
if (gst_pad_link (src_pad, sink_pad) != GST_PAD_LINK_OK)
1382
g_warning("ShellRecorder: can't link to sink pad");
1390
gst_object_unref (src_pad);
1392
gst_object_unref (sink_pad);
1398
recorder_update_memory_used_timeout (gpointer data)
1400
ShellRecorder *recorder = data;
1401
recorder->update_memory_used_timeout = 0;
1403
recorder_update_memory_used (recorder, TRUE);
1408
/* We throttle down the frequency which we recompute memory usage
1409
* and draw the buffer indicator to avoid cutting into performance.
1412
recorder_pipeline_on_memory_used_changed (ShellRecorderSrc *src,
1414
RecorderPipeline *pipeline)
1416
ShellRecorder *recorder = pipeline->recorder;
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,
1427
recorder_pipeline_free (RecorderPipeline *pipeline)
1429
if (pipeline->pipeline != NULL)
1430
gst_object_unref (pipeline->pipeline);
1432
if (pipeline->outfile != -1)
1433
close (pipeline->outfile);
1438
/* Function gets called on pipeline-global events; we use it to
1439
* know when the pipeline is finished.
1442
recorder_pipeline_bus_watch (GstBus *bus,
1443
GstMessage *message,
1446
RecorderPipeline *pipeline = data;
1448
switch (message->type)
1450
case GST_MESSAGE_EOS:
1451
recorder_pipeline_closed (pipeline);
1452
return FALSE; /* remove watch */
1453
case GST_MESSAGE_ERROR:
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 */
1467
/* Leave the watch in place */
1471
/* Clean up when the pipeline is finished
1474
recorder_pipeline_closed (RecorderPipeline *pipeline)
1476
g_signal_handlers_disconnect_by_func (pipeline->src,
1477
(gpointer) recorder_pipeline_on_memory_used_changed,
1480
gst_element_set_state (pipeline->pipeline, GST_STATE_NULL);
1482
if (pipeline->recorder)
1484
ShellRecorder *recorder = pipeline->recorder;
1485
if (pipeline == recorder->current_pipeline)
1487
/* Error case; force a close */
1488
recorder->current_pipeline = NULL;
1489
shell_recorder_close (recorder);
1492
recorder->pipelines = g_slist_remove (recorder->pipelines, pipeline);
1495
recorder_pipeline_free (pipeline);
1499
* Replaces '%T' in the passed pipeline with the thread count,
1500
* the maximum possible value is 64 (limit of what vp8enc supports)
1502
* It is assumes that %T occurs only once.
1505
substitute_thread_count (const char *pipeline)
1511
tmp = strstr (pipeline, "%T");
1514
return g_strdup (pipeline);
1516
#ifdef _SC_NPROCESSORS_ONLN
1518
int n_processors = sysconf (_SC_NPROCESSORS_ONLN); /* includes hyper-threading */
1519
n_threads = MIN (MAX (1, n_processors - 1), 64);
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);
1530
return g_string_free (result, FALSE);;
1534
recorder_open_pipeline (ShellRecorder *recorder)
1536
RecorderPipeline *pipeline;
1537
const char *pipeline_description;
1538
char *parsed_pipeline;
1539
GError *error = NULL;
1542
pipeline = g_new0(RecorderPipeline, 1);
1543
pipeline->recorder = recorder;
1544
pipeline->outfile = - 1;
1546
pipeline_description = recorder->pipeline_description;
1547
if (!pipeline_description)
1548
pipeline_description = DEFAULT_PIPELINE;
1550
parsed_pipeline = substitute_thread_count (pipeline_description);
1552
pipeline->pipeline = gst_parse_launch_full (parsed_pipeline, NULL,
1553
GST_PARSE_FLAG_FATAL_ERRORS,
1555
g_free (parsed_pipeline);
1557
if (pipeline->pipeline == NULL)
1559
g_warning ("ShellRecorder: failed to parse pipeline: %s", error->message);
1560
g_error_free (error);
1564
if (!recorder_pipeline_add_source (pipeline))
1567
if (!recorder_pipeline_add_sink (pipeline))
1570
gst_element_set_state (pipeline->pipeline, GST_STATE_PLAYING);
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);
1576
g_signal_connect (pipeline->src, "notify::memory-used",
1577
G_CALLBACK (recorder_pipeline_on_memory_used_changed), pipeline);
1579
recorder->current_pipeline = pipeline;
1580
recorder->pipelines = g_slist_prepend (recorder->pipelines, pipeline);
1585
recorder_pipeline_free (pipeline);
1591
recorder_close_pipeline (ShellRecorder *recorder)
1593
if (recorder->current_pipeline != NULL)
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
1599
shell_recorder_src_close (SHELL_RECORDER_SRC (recorder->current_pipeline->src));
1601
recorder->current_pipeline = NULL;
1602
recorder->filename_has_count = FALSE;
1607
* shell_recorder_new:
1608
* @stage: The #ClutterStage
1610
* Create a new #ShellRecorder to record movies of a #ClutterStage
1612
* Return value: The newly created #ShellRecorder object
1615
shell_recorder_new (ClutterStage *stage)
1617
return g_object_new (SHELL_TYPE_RECORDER,
1623
* shell_recorder_set_framerate:
1624
* @recorder: the #ShellRecorder
1625
* @framerate: Framerate used for resulting video in frames-per-second.
1627
* Sets the number of frames per second we configure for the GStreamer pipeline.
1629
* The default value is 15.
1632
shell_recorder_set_framerate (ShellRecorder *recorder,
1635
g_return_if_fail (SHELL_IS_RECORDER (recorder));
1637
recorder_set_framerate (recorder, framerate);
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.
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:
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
1660
* The default value is 'shell-%d%u-%c.ogg'.
1663
shell_recorder_set_filename (ShellRecorder *recorder,
1664
const char *filename)
1666
g_return_if_fail (SHELL_IS_RECORDER (recorder));
1668
recorder_set_filename (recorder, filename);
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.
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.
1688
* The default value is 'videorate ! theoraenc ! oggmux'
1691
shell_recorder_set_pipeline (ShellRecorder *recorder,
1692
const char *pipeline)
1694
g_return_if_fail (SHELL_IS_RECORDER (recorder));
1696
recorder_set_pipeline (recorder, pipeline);
1700
* shell_recorder_record:
1701
* @recorder: the #ShellRecorder
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.
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
1716
* Return value: %TRUE if recording was succesfully started
1719
shell_recorder_record (ShellRecorder *recorder)
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);
1725
if (recorder->current_pipeline)
1727
/* Adjust the start time so that the times in the stream ignore the
1730
recorder->start_time = recorder->start_time + (get_wall_time() - recorder->pause_time);
1734
if (!recorder_open_pipeline (recorder))
1737
recorder->start_time = get_wall_time();
1740
recorder->state = RECORDER_STATE_RECORDING;
1741
recorder_add_update_pointer_timeout (recorder);
1743
/* Set up repaint hook */
1744
recorder->repaint_hook_id = clutter_threads_add_repaint_func(recorder_repaint_hook, recorder->stage, NULL);
1746
/* Record an initial frame and also redraw with the indicator */
1747
clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
1749
/* We keep a ref while recording to let a caller start a recording then
1750
* drop their reference to the recorder
1752
g_object_ref (recorder);
1758
* shell_recorder_pause:
1759
* @recorder: the #ShellRecorder
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()
1768
shell_recorder_pause (ShellRecorder *recorder)
1770
g_return_if_fail (SHELL_IS_RECORDER (recorder));
1771
g_return_if_fail (recorder->state == RECORDER_STATE_RECORDING);
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
1777
clutter_actor_paint (CLUTTER_ACTOR (recorder->stage));
1779
if (recorder->filename_has_count)
1780
recorder_close_pipeline (recorder);
1782
recorder->state = RECORDER_STATE_PAUSED;
1783
recorder->pause_time = get_wall_time();
1785
/* Queue a redraw to remove the recording indicator */
1786
clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage));
1788
if (recorder->repaint_hook_id != 0)
1790
clutter_threads_remove_repaint_func (recorder->repaint_hook_id);
1791
recorder->repaint_hook_id = 0;
1796
* shell_recorder_close:
1797
* @recorder: the #ShellRecorder
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
1805
shell_recorder_close (ShellRecorder *recorder)
1807
g_return_if_fail (SHELL_IS_RECORDER (recorder));
1808
g_return_if_fail (recorder->state != RECORDER_STATE_CLOSED);
1810
if (recorder->state == RECORDER_STATE_RECORDING)
1811
shell_recorder_pause (recorder);
1813
recorder_remove_update_pointer_timeout (recorder);
1814
recorder_remove_redraw_timeout (recorder);
1815
recorder_close_pipeline (recorder);
1817
recorder->state = RECORDER_STATE_CLOSED;
1818
recorder->count = 0;
1819
g_free (recorder->unique);
1820
recorder->unique = NULL;
1822
/* Release the refcount we took when we started recording */
1823
g_object_unref (recorder);
1827
* shell_recorder_is_recording:
1829
* Determine if recording is currently in progress. (The recorder
1830
* is not paused or closed.)
1832
* Return value: %TRUE if the recorder is currently recording.
1835
shell_recorder_is_recording (ShellRecorder *recorder)
1837
g_return_val_if_fail (SHELL_IS_RECORDER (recorder), FALSE);
1839
return recorder->state == RECORDER_STATE_RECORDING;