/* TODO: single static mainloop */
+/* NOTE:
+ * Be careful what you do when the PulseAudio mainloop is held, which is to say
+ * within PulseAudio callbacks, or after pa_threaded_mainloop_lock().
+ * In particular, a VLC variable callback cannot be triggered nor deleted with
+ * the PulseAudio mainloop lock held, if the callback acquires the lock. */
+
struct aout_sys_t
{
pa_stream *stream; /**< PulseAudio playback stream object */
}
/* Sink */
+static void sink_list_cb(pa_context *c, const pa_sink_info *i, int eol,
+ void *userdata)
+{
+ aout_instance_t *aout = userdata;
+ vlc_value_t val, text;
+
+ if (eol)
+ return;
+ (void) c;
+
+ msg_Dbg(aout, "listing sink %s (%"PRIu32"): %s", i->name, i->index,
+ i->description);
+ val.i_int = i->index;
+ text.psz_string = (char *)i->description;
+ var_Change(aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
+}
+
static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol,
void *userdata)
{
sink_info_cb, aout);
if (likely(op != NULL))
pa_operation_unref(op);
+
+ /* Update the variable if someone else moved our stream */
+ var_Change(aout, "audio-device", VLC_VAR_SETVALUE,
+ &(vlc_value_t){ .i_int = idx }, NULL);
}
static void stream_overflow_cb(pa_stream *s, void *userdata)
if (pa_context_errno(sys->context) != PA_ERR_NODATA)
error(aout, "cannot determine latency", sys->context);
} else {
- mtime_t gap = aout_FifoFirstDate(aout, &aout->output.fifo) - mdate()
+ mtime_t gap = aout_FifoFirstDate(&aout->output.fifo) - mdate()
- latency;
if (gap > AOUT_PTS_TOLERANCE)
* If this function is changed to not always dequeue blocks, be sure to
* limit the queue size to a reasonable limit to avoid huge leaks. */
for (;;) {
- block_t *block = aout_FifoPop(aout, &aout->output.fifo);
+ block_t *block = aout_FifoPop(&aout->output.fifo);
if (block == NULL)
break;
if (pa_stream_write(s, ptr, len, data_free, 0, PA_SEEK_RELATIVE) < 0)
{
+ error(aout, "cannot write", sys->context);
block_Release(block);
- msg_Err(aout, "cannot write: %s",
- pa_strerror(pa_context_errno(sys->context)));
}
}
return 0;
}
+static int StreamMove(vlc_object_t *obj, const char *varname, vlc_value_t old,
+ vlc_value_t val, void *userdata)
+{
+ aout_instance_t *aout = (aout_instance_t *)obj;
+ aout_sys_t *sys = aout->output.p_sys;
+ pa_stream *s = userdata;
+ pa_operation *op;
+ uint32_t idx = pa_stream_get_index(s);
+ uint32_t sink_idx = val.i_int;
+
+ (void) varname; (void) old;
+
+ pa_threaded_mainloop_lock(sys->mainloop);
+ op = pa_context_move_sink_input_by_index(sys->context, idx, sink_idx,
+ NULL, NULL);
+ if (likely(op != NULL)) {
+ pa_operation_unref(op);
+ msg_Dbg(aout, "moving to sink %"PRIu32, sink_idx);
+ } else
+ error(aout, "cannot move sink", sys->context);
+ pa_threaded_mainloop_unlock(sys->mainloop);
+
+ return (op != NULL) ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+
/*****************************************************************************
* Open: open the audio device
*****************************************************************************/
static int Open(vlc_object_t *obj)
{
aout_instance_t *aout = (aout_instance_t *)obj;
+ pa_operation *op;
/* Sample format specification */
struct pa_sample_spec ss;
error(aout, "cannot connect stream", ctx);
goto fail;
}
- stream_moved_cb(s, aout);
const struct pa_buffer_attr *pba = pa_stream_get_buffer_attr(s);
msg_Dbg(aout, "using buffer metrics: maxlength=%u, tlength=%u, "
pba->maxlength, pba->tlength, pba->prebuf, pba->minreq);
aout->output.i_nb_samples = pba->minreq / pa_frame_size(&ss);
+
+ var_Create(aout, "audio-device", VLC_VAR_INTEGER|VLC_VAR_HASCHOICE);
+ var_Change(aout, "audio-device", VLC_VAR_SETTEXT,
+ &(vlc_value_t){ .psz_string = (char *)_("Audio device") },
+ NULL);
+ var_AddCallback (aout, "audio-device", StreamMove, s);
+ op = pa_context_get_sink_info_list(ctx, sink_list_cb, aout);
+ /* We may need to wait for completion... once LibVLC supports this */
+ if (op != NULL)
+ pa_operation_unref(op);
+ stream_moved_cb(s, aout);
pa_threaded_mainloop_unlock(mainloop);
aout->output.pf_play = Play;
pa_context *ctx = sys->context;
pa_stream *s = sys->stream;
+ if (s != NULL) {
+ /* The callback takes mainloop lock, so it CANNOT be held here! */
+ var_DelCallback (aout, "audio-device", StreamMove, s);
+ var_Destroy (aout, "audio-device");
+ }
+
pa_threaded_mainloop_lock(mainloop);
if (s != NULL) {
pa_operation *op;