Fix name of PulseAudio SD
[vlc.git] / modules / services_discovery / pulse.c
1 /**
2  * @file pulse.c
3  * @brief List of PulseAudio sources for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2011 Rémi Denis-Courmont
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <search.h>
28 #include <assert.h>
29
30 #include <vlc_common.h>
31 #include <vlc_plugin.h>
32 #include <vlc_services_discovery.h>
33 #include <pulse/pulseaudio.h>
34 #include "../audio_output/vlcpulse.h"
35
36 static int Open (vlc_object_t *);
37 static void Close (vlc_object_t *);
38
39 VLC_SD_PROBE_HELPER("pulse", "Audio capture", SD_CAT_DEVICES);
40
41 vlc_module_begin ()
42     set_shortname (N_("Audio capture"))
43     set_description (N_("Audio capture (PulseAudio)"))
44     set_category (CAT_PLAYLIST)
45     set_subcategory (SUBCAT_PLAYLIST_SD)
46     set_capability ("services_discovery", 0)
47     set_callbacks (Open, Close)
48     add_shortcut ("pulse", "pa", "pulseaudio", "audio")
49
50     VLC_SD_PROBE_SUBMODULE
51 vlc_module_end ()
52
53 struct services_discovery_sys_t
54 {
55     void                 *root;
56     pa_context           *context;
57     pa_threaded_mainloop *mainloop;
58 };
59
60 static void SourceCallback(pa_context *, const pa_source_info *, int, void *);
61 static void ContextCallback(pa_context *, pa_subscription_event_type_t,
62                             uint32_t, void *);
63
64 static int Open (vlc_object_t *obj)
65 {
66     services_discovery_t *sd = (services_discovery_t *)obj;
67     pa_operation *op;
68     pa_context *ctx;
69
70     services_discovery_sys_t *sys = malloc (sizeof (*sys));
71     if (unlikely(sys == NULL))
72         return VLC_ENOMEM;
73
74     ctx = vlc_pa_connect (obj, &sys->mainloop);
75     if (ctx == NULL)
76     {
77         free (sys);
78         return VLC_EGENERIC;
79     }
80
81     sd->p_sys = sys;
82     sys->context = ctx;
83     sys->root = NULL;
84
85     /* Subscribe for source events */
86     const pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_SOURCE;
87     pa_threaded_mainloop_lock (sys->mainloop);
88     pa_context_set_subscribe_callback (ctx, ContextCallback, sd);
89     op = pa_context_subscribe (ctx, mask, NULL, NULL);
90     if (likely(op != NULL))
91         pa_operation_unref (op);
92
93     /* Enumerate existing sources */
94     op = pa_context_get_source_info_list (ctx, SourceCallback, sd);
95     if (likely(op != NULL))
96     {
97         //while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
98         //    pa_threaded_mainloop_wait (sys->mainloop);
99         pa_operation_unref (op);
100     }
101     pa_threaded_mainloop_unlock (sys->mainloop);
102     return VLC_SUCCESS;
103 /*
104 error:
105     pa_threaded_mainloop_unlock (sys->mainloop);
106     vlc_pa_disconnect (obj, ctx, sys->mainloop);
107     free (sys);
108     return VLC_EGENERIC;*/
109 }
110
111 struct device
112 {
113     uint32_t index;
114     input_item_t *item;
115     services_discovery_t *sd;
116 };
117
118 static void DestroySource (void *data)
119 {
120     struct device *d = data;
121
122     if (d->sd)
123         services_discovery_RemoveItem (d->sd, d->item);
124     vlc_gc_decref (d->item);
125     free (d);
126 }
127
128 /**
129  * Compares two devices (to support binary search).
130  */
131 static int cmpsrc (const void *a, const void *b)
132 {
133     const uint32_t *pa = a, *pb = b;
134     uint32_t idxa = *pa, idxb = *pb;
135
136     return (idxa != idxb) ? ((idxa < idxb) ? -1 : +1) : 0;
137 }
138
139 /**
140  * Adds a source.
141  */
142 static int AddSource (services_discovery_t *sd, const pa_source_info *info)
143 {
144     services_discovery_sys_t *sys = sd->p_sys;
145
146     msg_Dbg (sd, "adding %s (%s)", info->name, info->description);
147
148     char *mrl;
149     if (unlikely(asprintf (&mrl, "pulse://%s", info->name) == -1))
150         return -1;
151
152     input_item_t *item = input_item_NewWithType (mrl, info->description,
153                                                  0, NULL, 0, -1,
154                                                  ITEM_TYPE_CARD);
155     free (mrl);
156     if (unlikely(item == NULL))
157         return -1;
158
159     struct device *d = malloc (sizeof (*d));
160     if (unlikely(d == NULL))
161     {
162         vlc_gc_decref (item);
163         return -1;
164     }
165     d->index = info->index;
166     d->item = item;
167     d->sd = NULL;
168
169     struct device **dp = tsearch (d, &sys->root, cmpsrc);
170     if (dp == NULL) /* Out-of-memory */
171     {
172         DestroySource (d);
173         return -1;
174     }
175     if (*dp != d) /* Replace existing source */
176     {
177         DestroySource (*dp);
178         *dp = d;
179     }
180
181     char *card;
182     if (info->card == PA_INVALID_INDEX
183      || unlikely(asprintf (&card, N_("Card %"PRIu32), info->card) == -1))
184         card = NULL;
185     services_discovery_AddItem (sd, item,
186                                 (card != NULL) ? card : N_("Generic"));
187     d->sd = sd;
188     free (card);
189     return 0;
190 }
191
192 static void SourceCallback(pa_context *ctx, const pa_source_info *i, int eol,
193                            void *userdata)
194 {
195     services_discovery_t *sd = userdata;
196
197     if (eol)
198         return;
199     AddSource (sd, i);
200     (void) ctx;
201 }
202
203 /**
204  * Removes a source (if present) by index.
205  */
206 static void RemoveSource (services_discovery_t *sd, uint32_t idx)
207 {
208     services_discovery_sys_t *sys = sd->p_sys;
209
210     struct device **dp = tfind (&idx, &sys->root, cmpsrc);
211     if (dp == NULL)
212         return;
213
214     struct device *d = *dp;
215     tdelete (d, &sys->root, cmpsrc);
216     DestroySource (d);
217 }
218
219 static void ContextCallback(pa_context *ctx, pa_subscription_event_type_t type,
220                             uint32_t idx, void *userdata)
221 {
222     services_discovery_t *sd = userdata;
223     pa_operation *op;
224
225     assert ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
226                                               == PA_SUBSCRIPTION_EVENT_SOURCE);
227     switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK)
228     {
229       case PA_SUBSCRIPTION_EVENT_NEW:
230       case PA_SUBSCRIPTION_EVENT_CHANGE:
231         op = pa_context_get_source_info_by_index(ctx, idx, SourceCallback, sd);
232         if (likely(op != NULL))
233             pa_operation_unref(op);
234         break;
235
236       case PA_SUBSCRIPTION_EVENT_REMOVE:
237         RemoveSource (sd, idx);
238         break;
239     }
240 }
241
242 static void Close (vlc_object_t *obj)
243 {
244     services_discovery_t *sd = (services_discovery_t *)obj;
245     services_discovery_sys_t *sys = sd->p_sys;
246
247     vlc_pa_disconnect (obj, sys->context, sys->mainloop);
248     tdestroy (sys->root, DestroySource);
249     free (sys);
250 }