Commit ed864e7f authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

PulseAudio output: rewrite

This should fix the deadlocks and memory starvation problems.
parent 9d6a0a6a
...@@ -21,51 +21,20 @@ ...@@ -21,51 +21,20 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/ *****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
# include "config.h" # include "config.h"
#endif #endif
#include <vlc_common.h> #include <vlc_common.h>
#include <vlc_plugin.h> #include <vlc_plugin.h>
#include <vlc_aout.h> #include <vlc_aout.h>
#include <vlc_cpu.h> #include <vlc_cpu.h>
#include <pulse/pulseaudio.h> #include <pulse/pulseaudio.h>
/*****************************************************************************
* aout_sys_t: Pulseaudio output method descriptor
*****************************************************************************
* This structure is part of the audio output thread descriptor.
* It describes the specific properties of an audio device.
*****************************************************************************/
struct aout_sys_t
{
struct pa_stream *stream; /**< PulseAudio playback stream object */
struct pa_context *context; /**< PulseAudio connection context */
struct pa_threaded_mainloop *mainloop; /**< PulseAudio event loop */
size_t buffer_size;
mtime_t start_date;
};
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int Open ( vlc_object_t * ); static int Open ( vlc_object_t * );
static void Close ( vlc_object_t * ); static void Close ( vlc_object_t * );
static void Play ( aout_instance_t * );
static void context_state_cb(pa_context *c, void *userdata);
static void stream_state_cb(pa_stream *s, void * userdata);
static void stream_request_cb(pa_stream *s, size_t length, void *userdata);
static void success_cb(pa_stream *s, int sucess, void *userdata);
/*****************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin () vlc_module_begin ()
set_shortname( "PulseAudio" ) set_shortname( "PulseAudio" )
set_description( N_("Pulseaudio audio output") ) set_description( N_("Pulseaudio audio output") )
...@@ -76,30 +45,221 @@ vlc_module_begin () ...@@ -76,30 +45,221 @@ vlc_module_begin ()
set_callbacks( Open, Close ) set_callbacks( Open, Close )
vlc_module_end () vlc_module_end ()
struct aout_sys_t
{
pa_stream *stream; /**< PulseAudio playback stream object */
pa_context *context; /**< PulseAudio connection context */
pa_threaded_mainloop *mainloop; /**< PulseAudio event loop */
//uint32_t byterate; /**< bytes per second */
};
/* Context helpers */
static void context_state_cb(pa_context *c, void *userdata)
{
pa_threaded_mainloop *mainloop = userdata;
switch (pa_context_get_state(c)) {
case PA_CONTEXT_READY:
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
pa_threaded_mainloop_signal(mainloop, 0);
default:
break;
}
}
static bool context_wait(pa_threaded_mainloop *mainloop, pa_context *context)
{
pa_context_state_t state;
while ((state = pa_context_get_state(context)) != PA_CONTEXT_READY) {
if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED)
return -1;
pa_threaded_mainloop_wait(mainloop);
}
return 0;
}
static void error(aout_instance_t *aout, const char *msg, pa_context *context)
{
msg_Err(aout, "%s: %s", msg, pa_strerror(pa_context_errno(context)));
}
/* Stream helpers */
static void stream_state_cb(pa_stream *s, void *userdata)
{
pa_threaded_mainloop *mainloop = userdata;
switch (pa_stream_get_state(s)) {
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
pa_threaded_mainloop_signal(mainloop, 0);
default:
break;
}
}
static void stream_moved_cb(pa_stream *s, void *userdata)
{
vlc_object_t *obj = userdata;
msg_Dbg(obj, "connected to device %s (%u)",
pa_stream_get_device_name(s),
pa_stream_get_device_index(s));
}
static int stream_wait(pa_threaded_mainloop *mainloop, pa_stream *stream)
{
pa_stream_state_t state;
while ((state = pa_stream_get_state(stream)) != PA_STREAM_READY) {
if (state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED)
return -1;
pa_threaded_mainloop_wait(mainloop);
}
return 0;
}
/* Memory free callback. The block_t address is in front of the data. */
static void data_free(void *data)
{
block_t **pp = data, *block;
memcpy(&block, pp - 1, sizeof (block));
block_Release(block);
}
static void *data_convert(block_t **pp)
{
block_t *block = *pp;
/* In most cases, there is enough head room, and this is really cheap: */
block = block_Realloc(block, sizeof (block), block->i_buffer);
*pp = block;
if (unlikely(block == NULL))
return NULL;
memcpy(block->p_buffer, &block, sizeof (block));
block->p_buffer += sizeof (block);
block->i_buffer -= sizeof (block);
return block->p_buffer;
}
/*****************************************************************************
* Play: play a sound samples buffer
*****************************************************************************/
static void Play(aout_instance_t *aout)
{
aout_sys_t *sys = aout->output.p_sys;
pa_stream *s = sys->stream;
/* Note: The core already holds the output FIFO lock at this point.
* Therefore we must not under any circumstances (try to) acquire the
* output FIFO lock while the PulseAudio threaded main loop lock is held
* (including from PulseAudio stream callbacks). Otherwise lock inversion
* will take place, and sooner or later a deadlock. */
pa_threaded_mainloop_lock(sys->mainloop);
if (pa_stream_is_corked(sys->stream) > 0) {
pa_operation *op = pa_stream_cork(s, 0, NULL, NULL);
if (op != NULL)
pa_operation_unref(op);
msg_Dbg(aout, "uncorking");
}
/* This function should be called by the LibVLC core a header of time,
* but not more than AOUT_MAX_PREPARE. The PulseAudio latency should be
* shorter than that (though it might not be the case with some evil piece
* of audio output hardware). So we need to prepend the buffer with zeroes
* to keep audio and video in sync. */
pa_usec_t latency;
int negative;
if (pa_stream_get_latency(s, &latency, &negative) < 0) {
/* Especially at start of stream, latency may not be known (yet). */
if (pa_context_errno(sys->context) != PA_ERR_NODATA)
error(aout, "cannot determine latency", sys->context);
latency = 0;
}
mtime_t gap = aout_FifoFirstDate(aout, &aout->output.fifo) - mdate()
- latency;
if (gap > AOUT_PTS_TOLERANCE) {
#if 0 /* FIXME: we need to take buffer status into account as well... */
size_t len = sys->byterate * gap / CLOCK_FREQ;
void *ptr = calloc(1, len);
msg_Dbg(aout, "buffer too early (%"PRId64" us), stuffing %zu bytes",
gap, len);
if (likely(ptr != NULL))
if (pa_stream_write(sys->stream, ptr, len, free,
0, PA_SEEK_RELATIVE) < 0)
free(ptr);
#endif
msleep(gap);
}
/*else if (latency != 0 && gap < -AOUT_PTS_TOLERANCE)
msg_Err(aout, "buffer too late (%"PRId64" us)", -gap);*/
#if 0 /* Fault injector to test underrun recovery */
static unsigned u = 0;
if ((++u % 500) == 0) {
msg_Err(aout, "fault injection");
msleep(CLOCK_FREQ*2);
}
#endif
/* This function is called exactly once per block in the output FIFO, so
* this for-loop is not necessary.
* 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);
if (block == NULL)
break;
const void *ptr = data_convert(&block);
if (unlikely(ptr == NULL))
break;
size_t len = block->i_buffer;
//mtime_t pts = block->i_pts, duration = block->i_length;
if (pa_stream_write(s, ptr, len, data_free, 0, PA_SEEK_RELATIVE) < 0)
{
block_Release(block);
msg_Err(aout, "cannot write: %s",
pa_strerror(pa_context_errno(sys->context)));
}
}
pa_threaded_mainloop_unlock(sys->mainloop);
}
/***************************************************************************** /*****************************************************************************
* Open: open the audio device * Open: open the audio device
*****************************************************************************/ *****************************************************************************/
static int Open ( vlc_object_t *p_this ) static int Open(vlc_object_t *obj)
{ {
aout_instance_t *p_aout = (aout_instance_t *)p_this; aout_instance_t *aout = (aout_instance_t *)obj;
/* Sample format specification */ /* Sample format specification */
struct pa_sample_spec ss; struct pa_sample_spec ss;
switch(p_aout->output.output.i_format) switch(aout->output.output.i_format)
{ {
case VLC_CODEC_F64B: case VLC_CODEC_F64B:
p_aout->output.output.i_format = VLC_CODEC_F32B; aout->output.output.i_format = VLC_CODEC_F32B;
case VLC_CODEC_F32B: case VLC_CODEC_F32B:
ss.format = PA_SAMPLE_FLOAT32BE; ss.format = PA_SAMPLE_FLOAT32BE;
break; break;
case VLC_CODEC_F64L: case VLC_CODEC_F64L:
p_aout->output.output.i_format = VLC_CODEC_F32L; aout->output.output.i_format = VLC_CODEC_F32L;
case VLC_CODEC_F32L: case VLC_CODEC_F32L:
ss.format = PA_SAMPLE_FLOAT32LE; ss.format = PA_SAMPLE_FLOAT32LE;
break; break;
case VLC_CODEC_FI32: case VLC_CODEC_FI32:
p_aout->output.output.i_format = VLC_CODEC_FL32; aout->output.output.i_format = VLC_CODEC_FL32;
ss.format = PA_SAMPLE_FLOAT32NE; ss.format = PA_SAMPLE_FLOAT32NE;
break; break;
case VLC_CODEC_S32B: case VLC_CODEC_S32B:
...@@ -121,28 +281,28 @@ static int Open ( vlc_object_t *p_this ) ...@@ -121,28 +281,28 @@ static int Open ( vlc_object_t *p_this )
ss.format = PA_SAMPLE_S16LE; ss.format = PA_SAMPLE_S16LE;
break; break;
case VLC_CODEC_S8: case VLC_CODEC_S8:
p_aout->output.output.i_format = VLC_CODEC_U8; aout->output.output.i_format = VLC_CODEC_U8;
case VLC_CODEC_U8: case VLC_CODEC_U8:
ss.format = PA_SAMPLE_U8; ss.format = PA_SAMPLE_U8;
break; break;
default: default:
if (HAVE_FPU) if (HAVE_FPU)
{ {
p_aout->output.output.i_format = VLC_CODEC_FL32; aout->output.output.i_format = VLC_CODEC_FL32;
ss.format = PA_SAMPLE_FLOAT32NE; ss.format = PA_SAMPLE_FLOAT32NE;
} }
else else
{ {
p_aout->output.output.i_format = VLC_CODEC_S16N; aout->output.output.i_format = VLC_CODEC_S16N;
ss.format = PA_SAMPLE_S16NE; ss.format = PA_SAMPLE_S16NE;
} }
break; break;
} }
ss.rate = p_aout->output.output.i_rate; ss.rate = aout->output.output.i_rate;
ss.channels = aout_FormatNbChannels(&p_aout->output.output); ss.channels = aout_FormatNbChannels(&aout->output.output);
if (!pa_sample_spec_valid(&ss)) { if (!pa_sample_spec_valid(&ss)) {
msg_Err(p_aout, "unsupported sample specification"); msg_Err(aout, "unsupported sample specification");
return VLC_EGENERIC; return VLC_EGENERIC;
} }
...@@ -150,312 +310,163 @@ static int Open ( vlc_object_t *p_this ) ...@@ -150,312 +310,163 @@ static int Open ( vlc_object_t *p_this )
struct pa_channel_map map; struct pa_channel_map map;
map.channels = 0; map.channels = 0;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_CENTER) if (aout->output.output.i_physical_channels & AOUT_CHAN_CENTER)
{ {
if (ss.channels == 1) if (ss.channels == 1)
map.map[map.channels++] = PA_CHANNEL_POSITION_MONO; map.map[map.channels++] = PA_CHANNEL_POSITION_MONO;
else else
map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_CENTER; map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_CENTER;
} }
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_LEFT) if (aout->output.output.i_physical_channels & AOUT_CHAN_LEFT)
map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_RIGHT) if (aout->output.output.i_physical_channels & AOUT_CHAN_RIGHT)
map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT; map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_REARCENTER) if (aout->output.output.i_physical_channels & AOUT_CHAN_REARCENTER)
map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_CENTER; map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_CENTER;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_REARLEFT) if (aout->output.output.i_physical_channels & AOUT_CHAN_REARLEFT)
map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_LEFT; map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_LEFT;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_REARRIGHT) if (aout->output.output.i_physical_channels & AOUT_CHAN_REARRIGHT)
map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_RIGHT; map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_RIGHT;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_MIDDLELEFT) if (aout->output.output.i_physical_channels & AOUT_CHAN_MIDDLELEFT)
map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_MIDDLERIGHT) if (aout->output.output.i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
if (p_aout->output.output.i_physical_channels & AOUT_CHAN_LFE) if (aout->output.output.i_physical_channels & AOUT_CHAN_LFE)
map.map[map.channels++] = PA_CHANNEL_POSITION_LFE; map.map[map.channels++] = PA_CHANNEL_POSITION_LFE;
for (unsigned i = 0; map.channels < ss.channels; i++) { for (unsigned i = 0; map.channels < ss.channels; i++) {
map.map[map.channels++] = PA_CHANNEL_POSITION_AUX0 + i; map.map[map.channels++] = PA_CHANNEL_POSITION_AUX0 + i;
msg_Warn(p_aout, "mapping channel %"PRIu8" to AUX%u", map.channels, i); msg_Warn(aout, "mapping channel %"PRIu8" to AUX%u", map.channels, i);
} }
if (!pa_channel_map_valid(&map)) { if (!pa_channel_map_valid(&map)) {
msg_Err(p_aout, "unsupported channel map"); msg_Err(aout, "unsupported channel map");
return VLC_EGENERIC; return VLC_EGENERIC;
} else { } else {
const char *name = pa_channel_map_to_pretty_name(&map); const char *name = pa_channel_map_to_pretty_name(&map);
msg_Dbg(p_aout, "using %s channel map", (name != NULL) ? name : "?"); msg_Dbg(aout, "using %s channel map", (name != NULL) ? name : "?");
} }
const pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING const pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING
| PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_AUTO_TIMING_UPDATE
| PA_STREAM_ADJUST_LATENCY; | PA_STREAM_ADJUST_LATENCY
| PA_STREAM_START_CORKED;
const uint32_t byterate = pa_bytes_per_second(&ss);
struct pa_buffer_attr attr; struct pa_buffer_attr attr;
/* Reduce overall latency to 100mS to reduce audible clicks /* no point in larger buffers on PA side than VLC */
* Also minreq and internal buffers are now 100ms to reduce resampling. attr.maxlength = byterate * AOUT_MAX_ADVANCE_TIME / CLOCK_FREQ;
* But it still should not drop samples even with USB sound cards. */ attr.tlength = byterate * AOUT_MAX_PREPARE_TIME / CLOCK_FREQ;
attr.tlength = pa_bytes_per_second(&ss)/10; attr.prebuf = byterate * AOUT_MIN_PREPARE_TIME / CLOCK_FREQ;
attr.maxlength = attr.tlength * 2;
attr.prebuf = -1;
attr.minreq = -1; attr.minreq = -1;
attr.fragsize = 0; /* not used for output */ attr.fragsize = 0; /* not used for output */
/* Allocate structures */ /* Allocate structures */
struct aout_sys_t *sys = malloc(sizeof(*sys)); aout_sys_t *sys = malloc(sizeof(*sys));
if (unlikely(sys == NULL)) if (unlikely(sys == NULL))
return VLC_ENOMEM; return VLC_ENOMEM;
sys->context = NULL;
sys->stream = NULL; sys->stream = NULL;
sys->start_date = VLC_TS_INVALID; //sys->byterate = byterate;
sys->mainloop = pa_threaded_mainloop_new(); /* Allocate threaded main loop */
if (unlikely(sys->mainloop == NULL)) { pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new();
if (unlikely(mainloop == NULL)) {
free(sys); free(sys);
return VLC_ENOMEM; return VLC_ENOMEM;
} }
sys->mainloop = mainloop;
char *ua = var_InheritString(p_aout, "user-agent"); if (pa_threaded_mainloop_start(mainloop) < 0) {
pa_threaded_mainloop_free(mainloop);
sys->context = pa_context_new(
pa_threaded_mainloop_get_api(sys->mainloop), ua);
free(ua);
if (unlikely(sys->context == NULL)) {
pa_threaded_mainloop_free(sys->mainloop);
free(sys); free(sys);
return VLC_ENOMEM; return VLC_ENOMEM;
} }
pa_threaded_mainloop_lock(mainloop);
p_aout->output.p_sys = sys;
pa_context_set_state_callback(sys->context, context_state_cb, sys); /* Connect to PulseAudio server */
if (pa_context_connect(sys->context, NULL, 0, NULL) < 0) { char *user_agent = var_InheritString(aout, "user-agent");
connect_fail: pa_context *ctx = pa_context_new(pa_threaded_mainloop_get_api(mainloop),
msg_Err(p_aout, "cannot connect to server: %s", user_agent);
pa_strerror(pa_context_errno(sys->context))); free(user_agent);
if (unlikely(ctx == NULL))
goto fail; goto fail;
} sys->context = ctx;
if (pa_threaded_mainloop_start(sys->mainloop) < 0) { pa_context_set_state_callback(ctx, context_state_cb, mainloop);
msg_Err(p_aout, "cannot start main loop"); if (pa_context_connect(ctx, NULL, 0, NULL) < 0
|| context_wait(mainloop, ctx)) {
error(aout, "cannot connect to server", ctx);
goto fail; goto fail;
} }
pa_threaded_mainloop_lock(sys->mainloop); /* Create a playback stream */
/* Wait until the context is ready */ pa_stream *s = pa_stream_new(ctx, "audio stream", &ss, &map);
while (pa_context_get_state(sys->context) != PA_CONTEXT_READY) { if (s == NULL) {
if (pa_context_get_state(sys->context) == PA_CONTEXT_FAILED) error(aout, "cannot create stream", ctx);
goto connect_fail; goto fail;
pa_threaded_mainloop_wait(sys->mainloop);
}
/* Create a stream */
sys->stream = pa_stream_new(sys->context, "audio stream", &ss, &map);
if (sys->stream == NULL) {
msg_Err(p_aout, "cannot create stream: %s",
pa_strerror(pa_context_errno(sys->context)));
goto unlock_and_fail;
}
pa_stream_set_state_callback(sys->stream, stream_state_cb, sys);
pa_stream_set_write_callback(sys->stream, stream_request_cb, p_aout);
if (pa_stream_connect_playback(sys->stream, NULL, &attr,
flags, NULL, NULL) < 0) {
stream_fail:
msg_Err(p_aout, "cannot connect stream: %s",
pa_strerror(pa_context_errno(sys->context)));
goto unlock_and_fail;
} }
sys->stream = s;
pa_stream_set_state_callback(s, stream_state_cb, mainloop);
pa_stream_set_moved_callback(s, stream_moved_cb, aout);
while (pa_stream_get_state(sys->stream) != PA_STREAM_READY) { if (pa_stream_connect_playback(s, NULL, &attr, flags, NULL, NULL) < 0
if (pa_stream_get_state(sys->stream) == PA_STREAM_FAILED) || stream_wait(mainloop, s)) {
goto stream_fail; error(aout, "cannot connect stream", ctx);
pa_threaded_mainloop_wait(sys->mainloop); goto fail;
} }
stream_moved_cb(s, aout);
msg_Dbg(p_aout, "Connected to device %s (%u, %ssuspended).", const struct pa_buffer_attr *pba = pa_stream_get_buffer_attr(s);
pa_stream_get_device_name(sys->stream), msg_Dbg(aout, "using buffer metrics: maxlength=%u, tlength=%u, "
pa_stream_get_device_index(sys->stream),
pa_stream_is_suspended(sys->stream) ? "" : "not ");
const struct pa_buffer_attr *pba = pa_stream_get_buffer_attr(sys->stream);
p_aout->output.i_nb_samples = pba->minreq / pa_frame_size(&ss);
/* Set buffersize from pulseaudio defined minrequest */
sys->buffer_size = pba->minreq;
msg_Dbg(p_aout, "using buffer metrics: maxlength=%u, tlength=%u, "
"prebuf=%u, minreq=%u", "prebuf=%u, minreq=%u",
pba->maxlength, pba->tlength, pba->prebuf, pba->minreq); pba->maxlength, pba->tlength, pba->prebuf, pba->minreq);
{
char sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
msg_Dbg(p_aout, "using sample specification: %s",
pa_sample_spec_snprint(sst, sizeof(sst),
pa_stream_get_sample_spec(sys->stream)));
}
{
char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
msg_Dbg(p_aout, "using channel map: %s",
pa_channel_map_snprint(cmt, sizeof(cmt),
pa_stream_get_channel_map(sys->stream)));
}
pa_threaded_mainloop_unlock(sys->mainloop);
p_aout->output.pf_play = Play; aout->output.i_nb_samples = pba->minreq / pa_frame_size(&ss);
aout_VolumeSoftInit(p_aout); pa_threaded_mainloop_unlock(mainloop);
aout->output.p_sys = sys;
aout->output.pf_play = Play;
aout_VolumeSoftInit(aout);
return VLC_SUCCESS; return VLC_SUCCESS;
unlock_and_fail:
pa_threaded_mainloop_unlock(sys->mainloop);
fail: fail:
Close(p_this); pa_threaded_mainloop_unlock(mainloop);
Close(obj);
return VLC_EGENERIC; return VLC_EGENERIC;
} }
/*****************************************************************************
* Play: play a sound samples buffer
*****************************************************************************/
static void Play(aout_instance_t * aout)
{
struct aout_sys_t *sys = aout->output.p_sys;
if (likely(sys->start_date != VLC_TS_INVALID))
return;
pa_threaded_mainloop_lock(sys->mainloop);
sys->start_date = aout_FifoFirstDate(aout, &aout->output.fifo);
pa_threaded_mainloop_unlock(sys->mainloop);
}
/***************************************************************************** /*****************************************************************************
* Close: close the audio device * Close: close the audio device
*****************************************************************************/ *****************************************************************************/
static void Close (vlc_object_t *obj) static void Close (vlc_object_t *obj)
{ {
aout_instance_t *aout = (aout_instance_t *)obj; aout_instance_t *aout = (aout_instance_t *)obj;
struct aout_sys_t *sys = aout->output.p_sys; aout_sys_t *sys = aout->output.p_sys;
pa_threaded_mainloop *mainloop = sys->mainloop;
if (sys->stream) { pa_context *ctx = sys->context;
pa_operation *o; pa_stream *s = sys->stream;
pa_threaded_mainloop_lock(sys->mainloop); pa_threaded_mainloop_lock(mainloop);
pa_stream_set_write_callback(sys->stream, NULL, NULL); if (s != NULL) {
pa_operation *op;
o = pa_stream_flush(sys->stream, success_cb, sys);
if (o != NULL) { pa_stream_set_write_callback(s, NULL, NULL);
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) op = pa_stream_flush(s, NULL, NULL);
pa_threaded_mainloop_wait(sys->mainloop); if (op != NULL)
pa_operation_unref(o); pa_operation_unref(op);
op = pa_stream_drain(s, NULL, NULL);
if (op != NULL)
pa_operation_unref(op);
pa_stream_disconnect(s);
pa_stream_unref(s);
} }
if (ctx != NULL)
o = pa_stream_drain(sys->stream, success_cb, sys); pa_context_unref(ctx);
if (o != NULL) { pa_threaded_mainloop_unlock(mainloop);
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) pa_threaded_mainloop_free(mainloop);
pa_threaded_mainloop_wait(sys->mainloop);
pa_operation_unref(o);
}
pa_threaded_mainloop_unlock(sys->mainloop);
}
pa_threaded_mainloop_stop(sys->mainloop);
if (sys->stream) {
pa_stream_disconnect(sys->stream);
pa_stream_unref(sys->stream);
}
pa_context_disconnect(sys->context);
pa_context_unref(sys->context);
pa_threaded_mainloop_free(sys->mainloop);
free(sys); free(sys);
} }
static void context_state_cb(pa_context *c, void *userdata)
{
struct aout_sys_t *sys = userdata;
switch (pa_context_get_state(c)) {
case PA_CONTEXT_READY:
case PA_CONTEXT_FAILED:
pa_threaded_mainloop_signal(sys->mainloop, 0);
default:
break;
}
}
static void stream_state_cb(pa_stream *s, void *userdata)
{
struct aout_sys_t *sys = userdata;
switch (pa_stream_get_state(s)) {
case PA_STREAM_READY:
case PA_STREAM_FAILED:
pa_threaded_mainloop_signal(sys->mainloop, 0);
default:
break;
}
}
/* Memory free callback. The block_t address is in front of the data. */
static void block_free_cb(void *data)
{
block_t **pp = data, *block;
memcpy(&block, pp - 1, sizeof (block));
block_Release(block);
}
static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
{
aout_instance_t *aout = userdata;
struct aout_sys_t *sys = aout->output.p_sys;
size_t buffer_size = sys->buffer_size;
do {
block_t *block = NULL;
if (sys->start_date != VLC_TS_INVALID) {
pa_usec_t latency;
int negative;
if (pa_stream_get_latency(s, &latency, &negative) < 0){
if (pa_context_errno(sys->context) != PA_ERR_NODATA) {
msg_Err(aout, "cannot determine latency: %s",
pa_strerror(pa_context_errno(sys->context)));
}
latency = 0;
}
//msg_Dbg(p_aout, "latency=%"PRId64, latency);
mtime_t next_date = mdate() + latency;
if (sys->start_date < next_date + AOUT_PTS_TOLERANCE)
block = aout_OutputNextBuffer(aout, next_date, 0);
}
if (block != NULL)
/* PA won't let us pass a reference to the buffer meta data... */
block = block_Realloc (block, sizeof (block), block->i_buffer);
if (block != NULL) {
memcpy(block->p_buffer, &block, sizeof (block));
block->p_buffer += sizeof (block);
block->i_buffer -= sizeof (block);
length -= block->i_buffer;
pa_stream_write(s, block->p_buffer, block->i_buffer,
block_free_cb, 0, PA_SEEK_RELATIVE);
} else {
void *data = pa_xmalloc(length);
memset(data, 0, length);
pa_stream_write(s, data, length, pa_xfree, 0, PA_SEEK_RELATIVE);
length = 0;
}
} while (length > buffer_size);
}
static void success_cb(pa_stream *s, int sucess, void *userdata)
{
struct aout_sys_t *sys = userdata;
(void) s;
(void) sucess;
pa_threaded_mainloop_signal(sys->mainloop, 0);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment