misc: medialibrary: ctx does not need dynamic lifetime
[vlc.git] / modules / audio_output / vlcpulse.c
blob4cdbc2293e67e25442ca7dd5b88ae4a7a52b5234
1 /**
2 * \file vlcpulse.c
3 * \brief PulseAudio support library for LibVLC plugins
4 */
5 /*****************************************************************************
6 * Copyright (C) 2009-2011 RĂ©mi Denis-Courmont
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.
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.
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 *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
27 #include <vlc_common.h>
28 #include <pulse/pulseaudio.h>
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>
37 const char vlc_module_name[] = "vlcpulse";
39 #undef vlc_pa_error
40 void vlc_pa_error (vlc_object_t *obj, const char *msg, pa_context *ctx)
42 msg_Err (obj, "%s: %s", msg, pa_strerror (pa_context_errno (ctx)));
45 static void context_state_cb (pa_context *ctx, void *userdata)
47 pa_threaded_mainloop *mainloop = userdata;
49 switch (pa_context_get_state(ctx))
51 case PA_CONTEXT_READY:
52 case PA_CONTEXT_FAILED:
53 case PA_CONTEXT_TERMINATED:
54 pa_threaded_mainloop_signal (mainloop, 0);
55 default:
56 break;
60 static bool context_wait (pa_context *ctx, pa_threaded_mainloop *mainloop)
62 pa_context_state_t state;
64 while ((state = pa_context_get_state (ctx)) != PA_CONTEXT_READY)
66 if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED)
67 return -1;
68 pa_threaded_mainloop_wait (mainloop);
70 return 0;
73 static void context_event_cb(pa_context *c, const char *name, pa_proplist *pl,
74 void *userdata)
76 vlc_object_t *obj = userdata;
78 msg_Warn (obj, "unhandled context event \"%s\"", name);
79 (void) c;
80 (void) pl;
83 /**
84 * Initializes the PulseAudio main loop and connects to the PulseAudio server.
85 * @return a PulseAudio context on success, or NULL on error
87 pa_context *vlc_pa_connect (vlc_object_t *obj, pa_threaded_mainloop **mlp)
89 msg_Dbg (obj, "using library version %s", pa_get_library_version ());
90 msg_Dbg (obj, " (compiled with version %s, protocol %u)",
91 pa_get_headers_version (), PA_PROTOCOL_VERSION);
93 /* Initialize main loop */
94 pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new ();
95 if (unlikely(mainloop == NULL))
96 return NULL;
98 if (pa_threaded_mainloop_start (mainloop) < 0)
100 pa_threaded_mainloop_free (mainloop);
101 return NULL;
104 /* Fill in context (client) properties */
105 char *ua = var_InheritString (obj, "user-agent");
106 pa_proplist *props = pa_proplist_new ();
107 if (likely(props != NULL))
109 char *str;
111 if (ua != NULL)
112 pa_proplist_sets (props, PA_PROP_APPLICATION_NAME, ua);
114 str = var_InheritString (obj, "app-id");
115 if (str != NULL)
117 pa_proplist_sets (props, PA_PROP_APPLICATION_ID, str);
118 free (str);
120 str = var_InheritString (obj, "app-version");
121 if (str != NULL)
123 pa_proplist_sets (props, PA_PROP_APPLICATION_VERSION, str);
124 free (str);
126 str = var_InheritString (obj, "app-icon-name");
127 if (str != NULL)
129 pa_proplist_sets (props, PA_PROP_APPLICATION_ICON_NAME, str);
130 free (str);
132 //pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE, _("C"));
133 pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE,
134 setlocale (LC_MESSAGES, NULL));
136 pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_ID, "%lu",
137 (unsigned long) getpid ());
138 //pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_BINARY,
139 // PACKAGE_NAME);
141 for (size_t max = sysconf (_SC_GETPW_R_SIZE_MAX), len = max % 1024 + 1024;
142 len < max; len += 1024)
144 struct passwd pwbuf, *pw;
145 char buf[len];
147 if (getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw) == 0)
149 if (pw != NULL)
150 pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_USER,
151 pw->pw_name);
152 break;
156 for (size_t max = sysconf (_SC_HOST_NAME_MAX), len = max % 1024 + 1024;
157 len < max; len += 1024)
159 char hostname[len];
161 if (gethostname (hostname, sizeof (hostname)) == 0)
163 pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_HOST,
164 hostname);
165 break;
169 const char *session = getenv ("XDG_SESSION_COOKIE");
170 if (session != NULL)
172 pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_MACHINE_ID,
173 "%.32s", session); /* XXX: is this valid? */
174 pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_SESSION_ID,
175 session);
179 /* Connect to PulseAudio daemon */
180 pa_context *ctx;
181 pa_mainloop_api *api;
183 pa_threaded_mainloop_lock (mainloop);
184 api = pa_threaded_mainloop_get_api (mainloop);
185 ctx = pa_context_new_with_proplist (api, ua, props);
186 free (ua);
187 if (props != NULL)
188 pa_proplist_free (props);
189 if (unlikely(ctx == NULL))
190 goto fail;
192 pa_context_set_state_callback (ctx, context_state_cb, mainloop);
193 pa_context_set_event_callback (ctx, context_event_cb, obj);
194 if (pa_context_connect (ctx, NULL, 0, NULL) < 0
195 || context_wait (ctx, mainloop))
197 vlc_pa_error (obj, "PulseAudio server connection failure", ctx);
198 pa_context_unref (ctx);
199 goto fail;
201 msg_Dbg (obj, "connected %s to %s as client #%"PRIu32,
202 pa_context_is_local (ctx) ? "locally" : "remotely",
203 pa_context_get_server (ctx), pa_context_get_index (ctx));
204 msg_Dbg (obj, "using protocol %"PRIu32", server protocol %"PRIu32,
205 pa_context_get_protocol_version (ctx),
206 pa_context_get_server_protocol_version (ctx));
208 pa_threaded_mainloop_unlock (mainloop);
209 *mlp = mainloop;
210 return ctx;
212 fail:
213 pa_threaded_mainloop_unlock (mainloop);
214 pa_threaded_mainloop_stop (mainloop);
215 pa_threaded_mainloop_free (mainloop);
216 return NULL;
220 * Closes a connection to PulseAudio.
222 void vlc_pa_disconnect (vlc_object_t *obj, pa_context *ctx,
223 pa_threaded_mainloop *mainloop)
225 pa_threaded_mainloop_lock (mainloop);
226 pa_context_disconnect (ctx);
227 pa_context_set_event_callback (ctx, NULL, NULL);
228 pa_context_set_state_callback (ctx, NULL, NULL);
229 pa_context_unref (ctx);
230 pa_threaded_mainloop_unlock (mainloop);
232 pa_threaded_mainloop_stop (mainloop);
233 pa_threaded_mainloop_free (mainloop);
234 (void) obj;
238 * Frees a timer event.
239 * \note Timer events can be created with pa_context_rttime_new().
240 * \warning This function must be called from the mainloop,
241 * or with the mainloop lock held.
243 void vlc_pa_rttime_free (pa_threaded_mainloop *mainloop, pa_time_event *e)
245 pa_mainloop_api *api = pa_threaded_mainloop_get_api (mainloop);
247 api->time_free (e);
250 #undef vlc_pa_get_latency
252 * Gets latency of a PulseAudio stream.
253 * \return the latency or VLC_TICK_INVALID on error.
255 vlc_tick_t vlc_pa_get_latency(vlc_object_t *obj, pa_context *ctx, pa_stream *s)
257 /* NOTE: pa_stream_get_latency() will report 0 rather than negative latency
258 * when the write index of a playback stream is behind its read index.
259 * playback streams. So use the lower-level pa_stream_get_timing_info()
260 * directly to obtain the correct write index and convert it to a time,
261 * and compute the correct latency value by subtracting the stream (read)
262 * time.
264 * On the read side, pa_stream_get_time() is used instead of
265 * pa_stream_get_timing_info() for the sake of interpolation. */
266 const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
267 const pa_timing_info *ti = pa_stream_get_timing_info(s);
269 if (ti == NULL) {
270 msg_Dbg(obj, "no timing infos");
271 return VLC_TICK_INVALID;
274 if (ti->write_index_corrupt) {
275 msg_Dbg(obj, "write index corrupt");
276 return VLC_TICK_INVALID;
279 pa_usec_t wt = pa_bytes_to_usec((uint64_t)ti->write_index, ss);
280 pa_usec_t rt;
282 if (pa_stream_get_time(s, &rt)) {
283 if (pa_context_errno(ctx) != PA_ERR_NODATA)
284 vlc_pa_error(obj, "unknown time", ctx);
285 return VLC_TICK_INVALID;
288 union { uint64_t u; int64_t s; } d;
289 d.u = wt - rt;
290 return d.s; /* non-overflowing unsigned to signed conversion */