screencast-pulseaudio.c 12 KB
Newer Older
Benjamin Berg's avatar
Benjamin Berg committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include "screencast-pulseaudio.h"
#include <pulse/glib-mainloop.h>
#include <pulse/pulseaudio.h>

G_DEFINE_AUTOPTR_CLEANUP_FUNC(pa_proplist, pa_proplist_free)

#define SCREENCAST_PA_SINK "gnome_screencast"
#define SCREENCAST_PA_MONITOR SCREENCAST_PA_SINK ".monitor"

struct _ScreencastPulseaudio
{
  GObject parent_instance;

  GTask            *init_task;

  pa_glib_mainloop *mainloop;
  pa_mainloop_api  *mainloop_api;
  pa_context       *context;
  guint             null_module_idx;

  pa_operation     *operation;
};

static void      screencast_pulseaudio_async_initable_iface_init (GAsyncInitableIface *iface);
static void      screencast_pulseaudio_async_initable_init_async (GAsyncInitable     *initable,
                                                                  int                 io_priority,
                                                                  GCancellable       *cancellable,
                                                                  GAsyncReadyCallback callback,
                                                                  gpointer            user_data);
static gboolean screencast_pulseaudio_async_initable_init_finish (GAsyncInitable *initable,
                                                                  GAsyncResult   *res,
                                                                  GError        **error);

G_DEFINE_TYPE_EXTENDED (ScreencastPulseaudio, screencast_pulseaudio, G_TYPE_OBJECT, 0,
                        G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
                                               screencast_pulseaudio_async_initable_iface_init);
                       )

enum {
	PROP_0,
	N_PROPS
};

static GParamSpec *properties [N_PROPS];


static void
screencast_pulseaudio_async_initable_iface_init (GAsyncInitableIface *iface)
{
  iface->init_async = screencast_pulseaudio_async_initable_init_async;
  iface->init_finish = screencast_pulseaudio_async_initable_init_finish;
}

static gboolean
return_idle_cb (gpointer user_data)
{
  GTask *task = user_data;
  GError *error = g_object_steal_data (G_OBJECT (task), "result");

  if (error)
    g_task_return_error (task, error);
  else
    g_task_return_boolean (task, TRUE);

  g_object_unref (task);

  return G_SOURCE_REMOVE;
}

/* Helper function to return an error from idle */
static void
return_idle_error (GTask *task, GError *error)
{
  g_assert (error);
  g_object_set_data_full (G_OBJECT (task), "result", error, (GDestroyNotify) g_error_free);
  g_idle_add (return_idle_cb, g_object_ref (task));
}

static void
return_idle_success (GTask *task)
{
  g_idle_add (return_idle_cb, g_object_ref (task));
}

static void
on_pa_null_module_loaded (pa_context *c,
                          uint32_t idx,
                          void *userdata)
{
  ScreencastPulseaudio *self = SCREENCAST_PULSEAUDIO (userdata);

  if (idx == PA_INVALID_INDEX)
    {
      g_debug ("ScreencastPulseaudio: Module load failed!");
      return_idle_error (self->init_task,
                         g_error_new (G_IO_ERROR,
                                      G_IO_ERROR_FAILED,
                                      "Failed to load NULL module for screencast PA sink"));
      g_clear_object (&self->init_task);
      return;
    }

  g_debug ("ScreencastPulseaudio: Module loaded, we are ready to grab audio! ");
  self->null_module_idx = idx;
  return_idle_success (self->init_task);
  g_clear_object (&self->init_task);
}

static void
on_pa_screencast_sink_got_info (pa_context *c,
                                const pa_sink_info *i,
                                int eol,
                                void *userdata)
{
  ScreencastPulseaudio *self = SCREENCAST_PULSEAUDIO (userdata);

  /* eol is negative for error, and 1 for end of list (with NULL sink info then) */

  /* We are only ever called once, as we cancel the operation
   * if we get what we want. */
  if (eol < 0 && pa_context_errno (self->context) != PA_ERR_NOENTITY)
    {
      g_debug ("ScreencastPulseaudio: Error querying sink info");
      return_idle_error (self->init_task,
                         g_error_new (G_IO_ERROR,
                                      G_IO_ERROR_FAILED,
                                      "Error querying sink from PA: %s",
                                      pa_strerror (pa_context_errno (self->context))));
      g_clear_object (&self->init_task);
      return;
    }

  if (eol == 0)
    {
      /* This is the case that we could query the sink, so it seems
       * like it exists already. Just double check things, and
       * return successful initialization.
       */

      /* Cancel the operation as we would be called a second time for
       * the list end which would cause am immediate successfull return. */
      pa_operation_cancel (self->operation);
      g_clear_pointer (&self->operation, pa_operation_unref);

      g_debug ("ScreencastPulseaudio: Error querying sink info");
      g_debug ("ScreencastPulseaudio: Got a sink info for the expected name");

      return_idle_success (self->init_task);
      g_clear_object (&self->init_task);
      return;
    }

  g_debug ("ScreencastPulseaudio: Sink does not exist yet, loading module");

  /* We have reached the list end without being cancelled first.
   * This means no screencast sink exist, and we need to create it. */
  self->operation = pa_context_load_module (self->context,
                                            "module-null-sink",
                                            "sink_name=gnome_screencast "
Benjamin Berg's avatar
Benjamin Berg committed
160
                                            "rate=48000 "
Benjamin Berg's avatar
Benjamin Berg committed
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
                                            "sink_properties=device.description=\"GNOME-Screencast\""
                                                            "device.class=\"sound\""
                                                            "device.icon_name=\"network-wireless\"",
                                            on_pa_null_module_loaded,
                                            self);
}

static void
screencast_pulseaudio_state_cb (pa_context *context,
                                void       *user_data)
{
  ScreencastPulseaudio *self = SCREENCAST_PULSEAUDIO (user_data);

  switch (pa_context_get_state (context))
    {
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
      break;

    case PA_CONTEXT_READY:
      if (!self->init_task)
        return;

      g_debug ("ScreencastPulseaudio: Querying sink info by name");
      self->operation = pa_context_get_sink_info_by_name (self->context,
                                                          SCREENCAST_PA_SINK,
                                                          on_pa_screencast_sink_got_info,
                                                          self);
      break;

    case PA_CONTEXT_FAILED:
    case PA_CONTEXT_TERMINATED:
      if (!self->init_task)
        return;

      g_debug ("ScreencastPulseaudio: PA context went into failed state during init");
      return_idle_error (self->init_task,
                         g_error_new (G_IO_ERROR,
                                      G_IO_ERROR_FAILED,
                                      "PA failed"));
      g_clear_object (&self->init_task);
      break;

    default:
      /* FIXME: */
      break;
    }
}

static void
screencast_pulseaudio_async_initable_init_async (GAsyncInitable     *initable,
                                                 int                 io_priority,
                                                 GCancellable       *cancellable,
                                                 GAsyncReadyCallback callback,
                                                 gpointer            user_data)
{
  ScreencastPulseaudio *self = SCREENCAST_PULSEAUDIO (initable);
  g_autoptr(pa_proplist) proplist = NULL;
  gint res;

  self->mainloop = pa_glib_mainloop_new (g_main_context_default ());
  self->mainloop_api = pa_glib_mainloop_get_api (self->mainloop);

  proplist = pa_proplist_new ();
  pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, "GNOME Screencast");
  pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "org.gnome.Screencast");
  /* pa_proplist_sets (proplist, PA_PROP_APPLICATION_ICON_NAME, ); */

  self->context = pa_context_new_with_proplist (self->mainloop_api, NULL, proplist);

  /* Create our task; we currently don't handle cancellation internally */
  self->init_task = g_task_new (initable, cancellable, callback, user_data);

  pa_context_set_state_callback (self->context,
                                 screencast_pulseaudio_state_cb,
                                 self);

  res = pa_context_connect (self->context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFLAGS, NULL);
  if (res < 0)
    {
      g_debug ("ScreencastPulseaudio: Error querying sink info");
      g_task_return_new_error (self->init_task,
                               G_IO_ERROR,
                               G_IO_ERROR_FAILED,
                               "Error connecting to PA: %s",
                               pa_strerror (pa_context_errno (self->context)));
      g_clear_object (&self->init_task);
      return;
    }

  /* Wait for us to be connected. */
}

static gboolean
screencast_pulseaudio_async_initable_init_finish (GAsyncInitable *initable,
                                                  GAsyncResult   *res,
                                                  GError        **error)
{
  return g_task_propagate_boolean (G_TASK (res), error);
}



ScreencastPulseaudio *
screencast_pulseaudio_new (void)
{
  return g_object_new (SCREENCAST_TYPE_PULSEAUDIO, NULL);
}

static void
screencast_pulseaudio_finalize (GObject *object)
{
  ScreencastPulseaudio *self = (ScreencastPulseaudio *)object;

  if (self->init_task)
    {
      g_task_return_new_error (self->init_task,
                               G_IO_ERROR,
                               G_IO_ERROR_CANCELLED,
                               "Object finalised, async init was cancelled.");
      g_clear_object (&self->init_task);
    }

  if (self->operation)
    pa_operation_cancel (self->operation);
  g_clear_pointer (&self->operation, pa_operation_unref);

  g_clear_pointer (&self->context, pa_context_unref);
  self->mainloop_api = NULL;
  g_clear_pointer (&self->mainloop, pa_glib_mainloop_free);

  G_OBJECT_CLASS (screencast_pulseaudio_parent_class)->finalize (object);
}

static void
screencast_pulseaudio_get_property (GObject    *object,
                                    guint       prop_id,
                                    GValue     *value,
                                    GParamSpec *pspec)
{
  ScreencastPulseaudio *self = SCREENCAST_PULSEAUDIO (object);

  switch (prop_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
screencast_pulseaudio_set_property (GObject      *object,
                                    guint         prop_id,
                                    const GValue *value,
                                    GParamSpec   *pspec)
{
  ScreencastPulseaudio *self = SCREENCAST_PULSEAUDIO (object);

  switch (prop_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
screencast_pulseaudio_class_init (ScreencastPulseaudioClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->finalize = screencast_pulseaudio_finalize;
	object_class->get_property = screencast_pulseaudio_get_property;
	object_class->set_property = screencast_pulseaudio_set_property;
}

static void
screencast_pulseaudio_init (ScreencastPulseaudio *self)
{
  self->null_module_idx = PA_INVALID_INDEX;
}

GstElement*
screencast_pulseaudio_get_source (ScreencastPulseaudio *self)
{
  g_autoptr(GstElement) src = NULL;

  g_assert (self->init_task == NULL);
  g_assert (self->context != NULL);

  src = gst_element_factory_make ("pulsesrc", "pulseaudio-source");

  g_object_set (src,
                "device", SCREENCAST_PA_MONITOR,
                "client-name", "GNOME-Screencast Audio Grabber",
                "do-timestamp", TRUE,
                "server", pa_context_get_server (self->context),
                NULL);

  return g_steal_pointer (&src);
}