]> git.sesse.net Git - vlc/blob - modules/audio_output/vlcpulse.c
direct3d11: catch texture mapping errors
[vlc] / modules / audio_output / vlcpulse.c
1 /**
2  * \file vlcpulse.c
3  * \brief PulseAudio support library for LibVLC plugins
4  */
5 /*****************************************************************************
6  * Copyright (C) 2009-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 <vlc_common.h>
28 #include <pulse/pulseaudio.h>
29
30 #include "audio_output/vlcpulse.h"
31 #include <assert.h>
32 #include <stdlib.h>
33 #include <locale.h>
34 #include <unistd.h>
35 #include <pwd.h>
36
37 #undef vlc_pa_error
38 void vlc_pa_error (vlc_object_t *obj, const char *msg, pa_context *ctx)
39 {
40     msg_Err (obj, "%s: %s", msg, pa_strerror (pa_context_errno (ctx)));
41 }
42
43 static void context_state_cb (pa_context *ctx, void *userdata)
44 {
45     pa_threaded_mainloop *mainloop = userdata;
46
47     switch (pa_context_get_state(ctx))
48     {
49         case PA_CONTEXT_READY:
50         case PA_CONTEXT_FAILED:
51         case PA_CONTEXT_TERMINATED:
52             pa_threaded_mainloop_signal (mainloop, 0);
53         default:
54             break;
55     }
56 }
57
58 static bool context_wait (pa_context *ctx, pa_threaded_mainloop *mainloop)
59 {
60     pa_context_state_t state;
61
62     while ((state = pa_context_get_state (ctx)) != PA_CONTEXT_READY)
63     {
64         if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED)
65             return -1;
66         pa_threaded_mainloop_wait (mainloop);
67     }
68     return 0;
69 }
70
71 static void context_event_cb(pa_context *c, const char *name, pa_proplist *pl,
72                              void *userdata)
73 {
74     vlc_object_t *obj = userdata;
75
76     msg_Warn (obj, "unhandled context event \"%s\"", name);
77     (void) c;
78     (void) pl;
79 }
80
81 /**
82  * Initializes the PulseAudio main loop and connects to the PulseAudio server.
83  * @return a PulseAudio context on success, or NULL on error
84  */
85 pa_context *vlc_pa_connect (vlc_object_t *obj, pa_threaded_mainloop **mlp)
86 {
87     msg_Dbg (obj, "using library version %s", pa_get_library_version ());
88     msg_Dbg (obj, " (compiled with version %s, protocol %u)",
89              pa_get_headers_version (), PA_PROTOCOL_VERSION);
90
91     /* Initialize main loop */
92     pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new ();
93     if (unlikely(mainloop == NULL))
94         return NULL;
95
96     if (pa_threaded_mainloop_start (mainloop) < 0)
97     {
98         pa_threaded_mainloop_free (mainloop);
99         return NULL;
100     }
101
102     /* Fill in context (client) properties */
103     char *ua = var_InheritString (obj, "user-agent");
104     pa_proplist *props = pa_proplist_new ();
105     if (likely(props != NULL))
106     {
107         char *str;
108
109         if (ua != NULL)
110             pa_proplist_sets (props, PA_PROP_APPLICATION_NAME, ua);
111
112         str = var_InheritString (obj, "app-id");
113         if (str != NULL)
114         {
115             pa_proplist_sets (props, PA_PROP_APPLICATION_ID, str);
116             free (str);
117         }
118         str = var_InheritString (obj, "app-version");
119         if (str != NULL)
120         {
121             pa_proplist_sets (props, PA_PROP_APPLICATION_VERSION, str);
122             free (str);
123         }
124         str = var_InheritString (obj, "app-icon-name");
125         if (str != NULL)
126         {
127             pa_proplist_sets (props, PA_PROP_APPLICATION_ICON_NAME, str);
128             free (str);
129         }
130         //pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE, _("C"));
131         pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE,
132                           setlocale (LC_MESSAGES, NULL));
133
134         pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_ID, "%lu",
135                           (unsigned long) getpid ());
136         //pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_BINARY,
137         //                  PACKAGE_NAME);
138
139         for (size_t max = sysconf (_SC_GETPW_R_SIZE_MAX), len = max % 1024 + 1024;
140              len < max; len += 1024)
141         {
142             struct passwd pwbuf, *pw;
143             char buf[len];
144
145             if (getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw) == 0)
146             {
147                 if (pw != NULL)
148                     pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_USER,
149                                       pw->pw_name);
150                 break;
151             }
152         }
153
154         for (size_t max = sysconf (_SC_HOST_NAME_MAX), len = max % 1024 + 1024;
155              len < max; len += 1024)
156         {
157             char hostname[len];
158
159             if (gethostname (hostname, sizeof (hostname)) == 0)
160             {
161                 pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_HOST,
162                                   hostname);
163                 break;
164             }
165         }
166
167         const char *session = getenv ("XDG_SESSION_COOKIE");
168         if (session != NULL)
169         {
170             pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_MACHINE_ID,
171                               "%.32s", session); /* XXX: is this valid? */
172             pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_SESSION_ID,
173                               session);
174         }
175     }
176
177     /* Connect to PulseAudio daemon */
178     pa_context *ctx;
179     pa_mainloop_api *api;
180
181     pa_threaded_mainloop_lock (mainloop);
182     api = pa_threaded_mainloop_get_api (mainloop);
183     ctx = pa_context_new_with_proplist (api, ua, props);
184     free (ua);
185     if (props != NULL)
186         pa_proplist_free (props);
187     if (unlikely(ctx == NULL))
188         goto fail;
189
190     pa_context_set_state_callback (ctx, context_state_cb, mainloop);
191     pa_context_set_event_callback (ctx, context_event_cb, obj);
192     if (pa_context_connect (ctx, NULL, 0, NULL) < 0
193      || context_wait (ctx, mainloop))
194     {
195         vlc_pa_error (obj, "PulseAudio server connection failure", ctx);
196         pa_context_unref (ctx);
197         goto fail;
198     }
199     msg_Dbg (obj, "connected %s to %s as client #%"PRIu32,
200              pa_context_is_local (ctx) ? "locally" : "remotely",
201              pa_context_get_server (ctx), pa_context_get_index (ctx));
202     msg_Dbg (obj, "using protocol %"PRIu32", server protocol %"PRIu32,
203              pa_context_get_protocol_version (ctx),
204              pa_context_get_server_protocol_version (ctx));
205
206     pa_threaded_mainloop_unlock (mainloop);
207     *mlp = mainloop;
208     return ctx;
209
210 fail:
211     pa_threaded_mainloop_unlock (mainloop);
212     pa_threaded_mainloop_stop (mainloop);
213     pa_threaded_mainloop_free (mainloop);
214     return NULL;
215 }
216
217 /**
218  * Closes a connection to PulseAudio.
219  */
220 void vlc_pa_disconnect (vlc_object_t *obj, pa_context *ctx,
221                         pa_threaded_mainloop *mainloop)
222 {
223     pa_threaded_mainloop_lock (mainloop);
224     pa_context_disconnect (ctx);
225     pa_context_set_event_callback (ctx, NULL, NULL);
226     pa_context_set_state_callback (ctx, NULL, NULL);
227     pa_context_unref (ctx);
228     pa_threaded_mainloop_unlock (mainloop);
229
230     pa_threaded_mainloop_stop (mainloop);
231     pa_threaded_mainloop_free (mainloop);
232     (void) obj;
233 }
234
235 /**
236  * Frees a timer event.
237  * \note Timer events can be created with pa_context_rttime_new().
238  * \warning This function must be called from the mainloop,
239  * or with the mainloop lock held.
240  */
241 void vlc_pa_rttime_free (pa_threaded_mainloop *mainloop, pa_time_event *e)
242 {
243     pa_mainloop_api *api = pa_threaded_mainloop_get_api (mainloop);
244
245     api->time_free (e);
246 }
247
248 #undef vlc_pa_get_latency
249 /**
250  * Gets latency of a PulseAudio stream.
251  * \return the latency or VLC_TS_INVALID on error.
252  */
253 mtime_t vlc_pa_get_latency(vlc_object_t *obj, pa_context *ctx, pa_stream *s)
254 {
255     pa_usec_t latency;
256     int negative;
257
258     if (pa_stream_get_latency(s, &latency, &negative)) {
259         if (pa_context_errno (ctx) != PA_ERR_NODATA)
260             vlc_pa_error(obj, "unknown latency", ctx);
261         return VLC_TS_INVALID;
262     }
263     return negative ? -latency : +latency;
264 }