3 * \brief PulseAudio support library for LibVLC plugins
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 *****************************************************************************/
27 #include <vlc_common.h>
28 #include <pulse/pulseaudio.h>
30 #include "audio_output/vlcpulse.h"
37 const char vlc_module_name
[] = "vlcpulse";
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);
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
)
68 pa_threaded_mainloop_wait (mainloop
);
73 static void context_event_cb(pa_context
*c
, const char *name
, pa_proplist
*pl
,
76 vlc_object_t
*obj
= userdata
;
78 msg_Warn (obj
, "unhandled context event \"%s\"", name
);
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
))
98 if (pa_threaded_mainloop_start (mainloop
) < 0)
100 pa_threaded_mainloop_free (mainloop
);
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
))
112 pa_proplist_sets (props
, PA_PROP_APPLICATION_NAME
, ua
);
114 str
= var_InheritString (obj
, "app-id");
117 pa_proplist_sets (props
, PA_PROP_APPLICATION_ID
, str
);
120 str
= var_InheritString (obj
, "app-version");
123 pa_proplist_sets (props
, PA_PROP_APPLICATION_VERSION
, str
);
126 str
= var_InheritString (obj
, "app-icon-name");
129 pa_proplist_sets (props
, PA_PROP_APPLICATION_ICON_NAME
, 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,
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
;
147 if (getpwuid_r (getuid (), &pwbuf
, buf
, sizeof (buf
), &pw
) == 0)
150 pa_proplist_sets (props
, PA_PROP_APPLICATION_PROCESS_USER
,
156 for (size_t max
= sysconf (_SC_HOST_NAME_MAX
), len
= max
% 1024 + 1024;
157 len
< max
; len
+= 1024)
161 if (gethostname (hostname
, sizeof (hostname
)) == 0)
163 pa_proplist_sets (props
, PA_PROP_APPLICATION_PROCESS_HOST
,
169 const char *session
= getenv ("XDG_SESSION_COOKIE");
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
,
179 /* Connect to PulseAudio daemon */
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
);
188 pa_proplist_free (props
);
189 if (unlikely(ctx
== NULL
))
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
);
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
);
213 pa_threaded_mainloop_unlock (mainloop
);
214 pa_threaded_mainloop_stop (mainloop
);
215 pa_threaded_mainloop_free (mainloop
);
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
);
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
);
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)
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
);
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
);
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
;
290 return d
.s
; /* non-overflowing unsigned to signed conversion */