PulseAudio: show device product name rather than card number
[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     services_discovery_RemoveItem (d->sd, d->item);
123     vlc_gc_decref (d->item);
124     free (d);
125 }
126
127 /**
128  * Compares two devices (to support binary search).
129  */
130 static int cmpsrc (const void *a, const void *b)
131 {
132     const uint32_t *pa = a, *pb = b;
133     uint32_t idxa = *pa, idxb = *pb;
134
135     return (idxa != idxb) ? ((idxa < idxb) ? -1 : +1) : 0;
136 }
137
138 /**
139  * Adds a source.
140  */
141 static int AddSource (services_discovery_t *sd, const pa_source_info *info)
142 {
143     services_discovery_sys_t *sys = sd->p_sys;
144
145     msg_Dbg (sd, "adding %s (%s)", info->name, info->description);
146
147     char *mrl;
148     if (unlikely(asprintf (&mrl, "pulse://%s", info->name) == -1))
149         return -1;
150
151     input_item_t *item = input_item_NewWithType (mrl, info->description,
152                                                  0, NULL, 0, -1,
153                                                  ITEM_TYPE_CARD);
154     free (mrl);
155     if (unlikely(item == NULL))
156         return -1;
157
158     struct device *d = malloc (sizeof (*d));
159     if (unlikely(d == NULL))
160     {
161         vlc_gc_decref (item);
162         return -1;
163     }
164     d->index = info->index;
165     d->item = item;
166
167     struct device **dp = tsearch (d, &sys->root, cmpsrc);
168     if (dp == NULL) /* Out-of-memory */
169     {
170         free (d);
171         vlc_gc_decref (item);
172         return -1;
173     }
174     if (*dp != d) /* Update existing source */
175     {
176         free (d);
177         d = *dp;
178         input_item_SetURI (d->item, item->psz_uri);
179         input_item_SetName (d->item, item->psz_name);
180         vlc_gc_decref (item);
181         return 0;
182     }
183
184     const char *card = pa_proplist_gets(info->proplist, "device.product.name");
185     services_discovery_AddItem (sd, item,
186                                 (card != NULL) ? card : N_("Generic"));
187     d->sd = sd;
188     return 0;
189 }
190
191 static void SourceCallback(pa_context *ctx, const pa_source_info *i, int eol,
192                            void *userdata)
193 {
194     services_discovery_t *sd = userdata;
195
196     if (eol)
197         return;
198     AddSource (sd, i);
199     (void) ctx;
200 }
201
202 /**
203  * Removes a source (if present) by index.
204  */
205 static void RemoveSource (services_discovery_t *sd, uint32_t idx)
206 {
207     services_discovery_sys_t *sys = sd->p_sys;
208
209     struct device **dp = tfind (&idx, &sys->root, cmpsrc);
210     if (dp == NULL)
211         return;
212
213     struct device *d = *dp;
214     tdelete (d, &sys->root, cmpsrc);
215     DestroySource (d);
216 }
217
218 static void ContextCallback(pa_context *ctx, pa_subscription_event_type_t type,
219                             uint32_t idx, void *userdata)
220 {
221     services_discovery_t *sd = userdata;
222     pa_operation *op;
223
224     assert ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
225                                               == PA_SUBSCRIPTION_EVENT_SOURCE);
226     switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK)
227     {
228       case PA_SUBSCRIPTION_EVENT_NEW:
229       case PA_SUBSCRIPTION_EVENT_CHANGE:
230         op = pa_context_get_source_info_by_index(ctx, idx, SourceCallback, sd);
231         if (likely(op != NULL))
232             pa_operation_unref(op);
233         break;
234
235       case PA_SUBSCRIPTION_EVENT_REMOVE:
236         RemoveSource (sd, idx);
237         break;
238     }
239 }
240
241 static void Close (vlc_object_t *obj)
242 {
243     services_discovery_t *sd = (services_discovery_t *)obj;
244     services_discovery_sys_t *sys = sd->p_sys;
245
246     vlc_pa_disconnect (obj, sys->context, sys->mainloop);
247     tdestroy (sys->root, DestroySource);
248     free (sys);
249 }