4 This file is part of gst-pulse.
6 gst-pulse is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
11 gst-pulse is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with gst-pulse; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
28 #include "pulsemixerctrl.h"
29 #include "pulsemixertrack.h"
30 #include "pulseutil.h"
32 GST_DEBUG_CATEGORY_EXTERN(pulse_debug
);
33 #define GST_CAT_DEFAULT pulse_debug
35 static void gst_pulsemixer_ctrl_context_state_cb(pa_context
*context
, void *userdata
) {
36 GstPulseMixerCtrl
*c
= GST_PULSEMIXER_CTRL(userdata
);
38 /* Called from the background thread! */
40 switch (pa_context_get_state(context
)) {
41 case PA_CONTEXT_READY
:
42 case PA_CONTEXT_TERMINATED
:
43 case PA_CONTEXT_FAILED
:
44 pa_threaded_mainloop_signal(c
->mainloop
, 0);
47 case PA_CONTEXT_UNCONNECTED
:
48 case PA_CONTEXT_CONNECTING
:
49 case PA_CONTEXT_AUTHORIZING
:
50 case PA_CONTEXT_SETTING_NAME
:
55 static void gst_pulsemixer_ctrl_sink_info_cb(pa_context
*context
, const pa_sink_info
*i
, int eol
, void *userdata
) {
56 GstPulseMixerCtrl
*c
= userdata
;
58 /* Called from the background thread! */
60 if (c
->outstandig_queries
> 0)
61 c
->outstandig_queries
--;
63 if (c
->ignore_queries
> 0 || c
->time_event
) {
65 if (c
->ignore_queries
> 0)
72 c
->operation_success
= 0;
73 pa_threaded_mainloop_signal(c
->mainloop
, 0);
81 g_free(c
->description
);
82 c
->name
= g_strdup(i
->name
);
83 c
->description
= g_strdup(i
->description
);
85 c
->channel_map
= i
->channel_map
;
86 c
->volume
= i
->volume
;
88 c
->type
= GST_PULSEMIXER_SINK
;
91 int i
= g_atomic_int_get(&c
->track
->flags
);
92 i
= (i
& ~GST_MIXER_TRACK_MUTE
) | (c
->muted
? GST_MIXER_TRACK_MUTE
: 0);
93 g_atomic_int_set(&c
->track
->flags
, i
);
96 c
->operation_success
= 1;
97 pa_threaded_mainloop_signal(c
->mainloop
, 0);
100 static void gst_pulsemixer_ctrl_source_info_cb(pa_context
*context
, const pa_source_info
*i
, int eol
, void *userdata
) {
101 GstPulseMixerCtrl
*c
= userdata
;
103 /* Called from the background thread! */
105 if (c
->outstandig_queries
> 0)
106 c
->outstandig_queries
--;
108 if (c
->ignore_queries
> 0 || c
->time_event
) {
110 if (c
->ignore_queries
> 0)
117 c
->operation_success
= 0;
118 pa_threaded_mainloop_signal(c
->mainloop
, 0);
126 g_free(c
->description
);
127 c
->name
= g_strdup(i
->name
);
128 c
->description
= g_strdup(i
->description
);
130 c
->channel_map
= i
->channel_map
;
131 c
->volume
= i
->volume
;
133 c
->type
= GST_PULSEMIXER_SOURCE
;
136 int i
= g_atomic_int_get(&c
->track
->flags
);
137 i
= (i
& ~GST_MIXER_TRACK_MUTE
) | (c
->muted
? GST_MIXER_TRACK_MUTE
: 0);
138 g_atomic_int_set(&c
->track
->flags
, i
);
141 c
->operation_success
= 1;
142 pa_threaded_mainloop_signal(c
->mainloop
, 0);
145 static void gst_pulsemixer_ctrl_subscribe_cb(pa_context
*context
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
146 GstPulseMixerCtrl
*c
= GST_PULSEMIXER_CTRL(userdata
);
147 pa_operation
*o
= NULL
;
149 /* Called from the background thread! */
154 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) != PA_SUBSCRIPTION_EVENT_CHANGE
)
157 if (c
->type
== GST_PULSEMIXER_SINK
)
158 o
= pa_context_get_sink_info_by_index(c
->context
, c
->index
, gst_pulsemixer_ctrl_sink_info_cb
, c
);
160 o
= pa_context_get_source_info_by_index(c
->context
, c
->index
, gst_pulsemixer_ctrl_source_info_cb
, c
);
163 GST_WARNING("Failed to get sink info: %s", pa_strerror(pa_context_errno(c
->context
)));
167 pa_operation_unref(o
);
169 c
->outstandig_queries
++;
172 static void gst_pulsemixer_ctrl_success_cb(pa_context
*context
, int success
, void *userdata
) {
173 GstPulseMixerCtrl
*c
= (GstPulseMixerCtrl
*) userdata
;
175 c
->operation_success
= success
;
176 pa_threaded_mainloop_signal(c
->mainloop
, 0);
179 #define CHECK_DEAD_GOTO(c, label) do { \
180 if (!(c)->context || pa_context_get_state((c)->context) != PA_CONTEXT_READY) { \
181 GST_WARNING("Not connected: %s", (c)->context ? pa_strerror(pa_context_errno((c)->context)) : "NULL"); \
186 static gboolean
gst_pulsemixer_ctrl_open(GstPulseMixerCtrl
*c
) {
188 gchar
*name
= gst_pulse_client_name();
189 pa_operation
*o
= NULL
;
193 c
->mainloop
= pa_threaded_mainloop_new();
194 g_assert(c
->mainloop
);
196 e
= pa_threaded_mainloop_start(c
->mainloop
);
199 pa_threaded_mainloop_lock(c
->mainloop
);
201 if (!(c
->context
= pa_context_new(pa_threaded_mainloop_get_api(c
->mainloop
), name
))) {
202 GST_WARNING("Failed to create context");
203 goto unlock_and_fail
;
206 pa_context_set_state_callback(c
->context
, gst_pulsemixer_ctrl_context_state_cb
, c
);
207 pa_context_set_subscribe_callback(c
->context
, gst_pulsemixer_ctrl_subscribe_cb
, c
);
209 if (pa_context_connect(c
->context
, c
->server
, 0, NULL
) < 0) {
210 GST_WARNING("Failed to connect context: %s", pa_strerror(pa_context_errno(c
->context
)));
211 goto unlock_and_fail
;
214 /* Wait until the context is ready */
215 pa_threaded_mainloop_wait(c
->mainloop
);
217 if (pa_context_get_state(c
->context
) != PA_CONTEXT_READY
) {
218 GST_WARNING("Failed to connect context: %s", pa_strerror(pa_context_errno(c
->context
)));
219 goto unlock_and_fail
;
222 /* Subscribe to events */
224 if (!(o
= pa_context_subscribe(c
->context
, PA_SUBSCRIPTION_MASK_SINK
|PA_SUBSCRIPTION_MASK_SOURCE
, gst_pulsemixer_ctrl_success_cb
, c
))) {
225 GST_WARNING("Failed to subscribe to events: %s", pa_strerror(pa_context_errno(c
->context
)));
226 goto unlock_and_fail
;
229 c
->operation_success
= 0;
230 while (pa_operation_get_state(o
) != PA_OPERATION_DONE
) {
231 pa_threaded_mainloop_wait(c
->mainloop
);
232 CHECK_DEAD_GOTO(c
, unlock_and_fail
);
235 if (!c
->operation_success
) {
236 GST_WARNING("Failed to subscribe to events: %s", pa_strerror(pa_context_errno(c
->context
)));
237 goto unlock_and_fail
;
242 if (c
->type
== GST_PULSEMIXER_UNKNOWN
|| c
->type
== GST_PULSEMIXER_SINK
) {
243 if (!(o
= pa_context_get_sink_info_by_name(c
->context
, c
->device
, gst_pulsemixer_ctrl_sink_info_cb
, c
))) {
244 GST_WARNING("Failed to get sink info: %s", pa_strerror(pa_context_errno(c
->context
)));
245 goto unlock_and_fail
;
248 c
->operation_success
= 0;
249 while (pa_operation_get_state(o
) != PA_OPERATION_DONE
) {
250 pa_threaded_mainloop_wait(c
->mainloop
);
251 CHECK_DEAD_GOTO(c
, unlock_and_fail
);
254 pa_operation_unref(o
);
257 if (!c
->operation_success
&& (c
->type
== GST_PULSEMIXER_SINK
|| pa_context_errno(c
->context
) != PA_ERR_NOENTITY
)) {
258 GST_WARNING("Failed to get sink info: %s", pa_strerror(pa_context_errno(c
->context
)));
259 goto unlock_and_fail
;
263 if (c
->type
== GST_PULSEMIXER_UNKNOWN
|| c
->type
== GST_PULSEMIXER_SOURCE
) {
264 if (!(o
= pa_context_get_source_info_by_name(c
->context
, c
->device
, gst_pulsemixer_ctrl_source_info_cb
, c
))) {
265 GST_WARNING("Failed to get source info: %s", pa_strerror(pa_context_errno(c
->context
)));
266 goto unlock_and_fail
;
269 c
->operation_success
= 0;
270 while (pa_operation_get_state(o
) != PA_OPERATION_DONE
) {
271 pa_threaded_mainloop_wait(c
->mainloop
);
272 CHECK_DEAD_GOTO(c
, unlock_and_fail
);
275 pa_operation_unref(o
);
278 if (!c
->operation_success
) {
279 GST_WARNING("Failed to get source info: %s", pa_strerror(pa_context_errno(c
->context
)));
280 goto unlock_and_fail
;
284 g_assert(c
->type
!= GST_PULSEMIXER_UNKNOWN
);
286 c
->track
= gst_pulsemixer_track_new(c
);
287 c
->tracklist
= g_list_append(c
->tracklist
, c
->track
);
289 pa_threaded_mainloop_unlock(c
->mainloop
);
297 pa_operation_unref(o
);
300 pa_threaded_mainloop_unlock(c
->mainloop
);
307 static void gst_pulsemixer_ctrl_close(GstPulseMixerCtrl
*c
) {
311 pa_threaded_mainloop_stop(c
->mainloop
);
314 pa_context_disconnect(c
->context
);
315 pa_context_unref(c
->context
);
320 pa_threaded_mainloop_free(c
->mainloop
);
322 c
->time_event
= NULL
;
326 g_list_free(c
->tracklist
);
331 GST_PULSEMIXER_TRACK(c
->track
)->control
= NULL
;
332 g_object_unref(c
->track
);
337 GstPulseMixerCtrl
* gst_pulsemixer_ctrl_new(const gchar
*server
, const gchar
*device
, GstPulseMixerType type
) {
338 GstPulseMixerCtrl
*c
= NULL
;
340 c
= g_new(GstPulseMixerCtrl
, 1);
342 c
->server
= g_strdup(server
);
343 c
->device
= g_strdup(device
);
347 c
->ignore_queries
= c
->outstandig_queries
= 0;
349 pa_cvolume_mute(&c
->volume
, PA_CHANNELS_MAX
);
350 pa_channel_map_init(&c
->channel_map
);
352 c
->index
= PA_INVALID_INDEX
;
355 c
->description
= NULL
;
357 c
->time_event
= NULL
;
358 c
->update_volume
= c
->update_mute
= FALSE
;
360 if (!(gst_pulsemixer_ctrl_open(c
))) {
361 gst_pulsemixer_ctrl_free(c
);
368 void gst_pulsemixer_ctrl_free(GstPulseMixerCtrl
*c
) {
371 gst_pulsemixer_ctrl_close(c
);
376 g_free(c
->description
);
380 const GList
* gst_pulsemixer_ctrl_list_tracks(GstPulseMixerCtrl
*c
) {
386 static void gst_pulsemixer_ctrl_timeout_event(pa_mainloop_api
*a
, pa_time_event
*e
, const struct timeval
*tv
, void *userdata
) {
388 GstPulseMixerCtrl
*c
= GST_PULSEMIXER_CTRL(userdata
);
390 if (c
->update_volume
) {
391 if (c
->type
== GST_PULSEMIXER_SINK
)
392 o
= pa_context_set_sink_volume_by_index(c
->context
, c
->index
, &c
->volume
, NULL
, NULL
);
394 o
= pa_context_set_source_volume_by_index(c
->context
, c
->index
, &c
->volume
, NULL
, NULL
);
397 GST_WARNING("Failed to set device volume: %s", pa_strerror(pa_context_errno(c
->context
)));
399 pa_operation_unref(o
);
401 c
->update_volume
= FALSE
;
404 if (c
->update_mute
) {
405 if (c
->type
== GST_PULSEMIXER_SINK
)
406 o
= pa_context_set_sink_mute_by_index(c
->context
, c
->index
, !!c
->muted
, NULL
, NULL
);
408 o
= pa_context_set_source_mute_by_index(c
->context
, c
->index
, !!c
->muted
, NULL
, NULL
);
411 GST_WARNING("Failed to set device mute: %s", pa_strerror(pa_context_errno(c
->context
)));
413 pa_operation_unref(o
);
415 c
->update_mute
= FALSE
;
418 /* Make sure that all outstanding queries are being ignored */
419 c
->ignore_queries
= c
->outstandig_queries
;
421 g_assert(e
== c
->time_event
);
423 c
->time_event
= NULL
;
426 #define UPDATE_DELAY 50000
428 static void restart_time_event(GstPulseMixerCtrl
*c
) {
434 /* Updating the volume too often will cause a lot of traffic
435 * when accessing a networked server. Therefore we make sure
436 * to update the volume only once every 50ms */
438 pa_mainloop_api
*api
= pa_threaded_mainloop_get_api(c
->mainloop
);
439 c
->time_event
= api
->time_new(api
, pa_timeval_add(pa_gettimeofday(&tv
), UPDATE_DELAY
), gst_pulsemixer_ctrl_timeout_event
, c
);
442 void gst_pulsemixer_ctrl_set_volume(GstPulseMixerCtrl
*c
, GstMixerTrack
*track
, gint
*volumes
) {
447 g_assert(track
== c
->track
);
449 pa_threaded_mainloop_lock(c
->mainloop
);
451 for (i
= 0; i
< c
->channel_map
.channels
; i
++)
452 v
.values
[i
] = (pa_volume_t
) volumes
[i
];
454 v
.channels
= c
->channel_map
.channels
;
457 c
->update_volume
= TRUE
;
459 restart_time_event(c
);
461 pa_threaded_mainloop_unlock(c
->mainloop
);
464 void gst_pulsemixer_ctrl_get_volume(GstPulseMixerCtrl
*c
, GstMixerTrack
*track
, gint
*volumes
) {
468 g_assert(track
== c
->track
);
470 pa_threaded_mainloop_lock(c
->mainloop
);
472 for (i
= 0; i
< c
->channel_map
.channels
; i
++)
473 volumes
[i
] = c
->volume
.values
[i
];
475 pa_threaded_mainloop_unlock(c
->mainloop
);
478 void gst_pulsemixer_ctrl_set_record(GstPulseMixerCtrl
*c
, GstMixerTrack
*track
, gboolean record
) {
480 g_assert(track
== c
->track
);
483 void gst_pulsemixer_ctrl_set_mute(GstPulseMixerCtrl
*c
, GstMixerTrack
*track
, gboolean mute
) {
485 g_assert(track
== c
->track
);
487 pa_threaded_mainloop_lock(c
->mainloop
);
490 c
->update_mute
= TRUE
;
493 int i
= g_atomic_int_get(&c
->track
->flags
);
494 i
= (i
& ~GST_MIXER_TRACK_MUTE
) | (c
->muted
? GST_MIXER_TRACK_MUTE
: 0);
495 g_atomic_int_set(&c
->track
->flags
, i
);
498 restart_time_event(c
);
500 pa_threaded_mainloop_unlock(c
->mainloop
);