2007-10-18 13:36:59 +00:00
|
|
|
/*
|
|
|
|
* PulseAudio audio output driver.
|
|
|
|
* Copyright (C) 2006 Lennart Poettering
|
|
|
|
* Copyright (C) 2007 Reimar Doeffinger
|
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2007-10-18 13:36:59 +00:00
|
|
|
*
|
2017-05-08 12:09:49 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2007-10-18 13:36:59 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2007-10-18 13:36:59 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2017-05-08 12:09:49 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2007-10-18 13:36:59 +00:00
|
|
|
*
|
2017-05-08 12:09:49 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2007-10-18 13:36:59 +00:00
|
|
|
*/
|
2008-05-14 18:02:27 +00:00
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdbool.h>
|
2007-10-18 13:36:59 +00:00
|
|
|
#include <string.h>
|
2014-01-07 22:50:22 +00:00
|
|
|
#include <stdint.h>
|
2016-07-14 16:11:14 +00:00
|
|
|
#include <math.h>
|
2014-05-29 21:56:48 +00:00
|
|
|
#include <pthread.h>
|
2007-10-18 13:36:59 +00:00
|
|
|
|
|
|
|
#include <pulse/pulseaudio.h>
|
|
|
|
|
|
|
|
#include "config.h"
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "audio/format.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/msg.h"
|
2014-04-15 20:42:15 +00:00
|
|
|
#include "options/m_option.h"
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "ao.h"
|
2014-03-07 14:24:32 +00:00
|
|
|
#include "internal.h"
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2016-07-14 16:11:14 +00:00
|
|
|
#define VOL_PA2MP(v) ((v) * 100.0 / PA_VOLUME_NORM)
|
|
|
|
#define VOL_MP2PA(v) lrint((v) * PA_VOLUME_NORM / 100)
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
struct priv {
|
|
|
|
// PulseAudio playback stream object
|
|
|
|
struct pa_stream *stream;
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
// PulseAudio connection context
|
|
|
|
struct pa_context *context;
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
// Main event loop object
|
|
|
|
struct pa_threaded_mainloop *mainloop;
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2012-01-17 06:55:04 +00:00
|
|
|
// temporary during control()
|
|
|
|
struct pa_sink_input_info pi;
|
2009-02-19 14:00:33 +00:00
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
int retval;
|
2013-07-21 20:13:11 +00:00
|
|
|
|
2014-05-29 21:56:48 +00:00
|
|
|
// for wakeup handling
|
|
|
|
pthread_mutex_t wakeup_lock;
|
|
|
|
pthread_cond_t wakeup;
|
|
|
|
int wakeup_status;
|
|
|
|
|
2013-07-21 20:13:11 +00:00
|
|
|
char *cfg_host;
|
2014-01-07 22:50:22 +00:00
|
|
|
int cfg_buffer;
|
2014-07-26 21:19:14 +00:00
|
|
|
int cfg_latency_hacks;
|
2019-05-05 04:53:41 +00:00
|
|
|
int cfg_allow_suspended;
|
2012-03-24 15:28:38 +00:00
|
|
|
};
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2013-08-22 21:12:35 +00:00
|
|
|
#define GENERIC_ERR_MSG(str) \
|
|
|
|
MP_ERR(ao, str": %s\n", \
|
|
|
|
pa_strerror(pa_context_errno(((struct priv *)ao->priv)->context)))
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
static void context_state_cb(pa_context *c, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
struct priv *priv = ao->priv;
|
2007-10-18 13:36:59 +00:00
|
|
|
switch (pa_context_get_state(c)) {
|
2012-03-24 15:28:38 +00:00
|
|
|
case PA_CONTEXT_READY:
|
|
|
|
case PA_CONTEXT_TERMINATED:
|
|
|
|
case PA_CONTEXT_FAILED:
|
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
|
|
|
break;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-12 16:00:52 +00:00
|
|
|
static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
|
|
|
|
uint32_t idx, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
int type = t & PA_SUBSCRIPTION_MASK_SINK;
|
|
|
|
int fac = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
|
|
|
|
if ((type == PA_SUBSCRIPTION_EVENT_NEW || type == PA_SUBSCRIPTION_EVENT_REMOVE)
|
|
|
|
&& fac == PA_SUBSCRIPTION_EVENT_SINK)
|
|
|
|
{
|
|
|
|
ao_hotplug_event(ao);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void context_success_cb(pa_context *c, int success, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
priv->retval = success;
|
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
static void stream_state_cb(pa_stream *s, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
struct priv *priv = ao->priv;
|
2007-10-18 13:36:59 +00:00
|
|
|
switch (pa_stream_get_state(s)) {
|
2012-03-24 15:28:38 +00:00
|
|
|
case PA_STREAM_FAILED:
|
2015-01-11 03:14:41 +00:00
|
|
|
MP_VERBOSE(ao, "Stream failed.\n");
|
|
|
|
ao_request_reload(ao);
|
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
|
|
|
break;
|
|
|
|
case PA_STREAM_READY:
|
2012-03-24 15:28:38 +00:00
|
|
|
case PA_STREAM_TERMINATED:
|
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
|
|
|
break;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-29 21:56:48 +00:00
|
|
|
static void wakeup(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pthread_mutex_lock(&priv->wakeup_lock);
|
|
|
|
priv->wakeup_status = 1;
|
|
|
|
pthread_cond_signal(&priv->wakeup);
|
|
|
|
pthread_mutex_unlock(&priv->wakeup_lock);
|
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
struct priv *priv = ao->priv;
|
2014-05-29 21:56:48 +00:00
|
|
|
wakeup(ao);
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2014-07-22 17:22:31 +00:00
|
|
|
static int wait_audio(struct ao *ao, pthread_mutex_t *lock)
|
2014-05-29 21:56:48 +00:00
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
// We don't use this mutex, because pulse like to call stream_request_cb
|
|
|
|
// while we have the central mutex held.
|
|
|
|
pthread_mutex_unlock(lock);
|
|
|
|
pthread_mutex_lock(&priv->wakeup_lock);
|
|
|
|
while (!priv->wakeup_status)
|
|
|
|
pthread_cond_wait(&priv->wakeup, &priv->wakeup_lock);
|
|
|
|
priv->wakeup_status = 0;
|
|
|
|
pthread_mutex_unlock(&priv->wakeup_lock);
|
|
|
|
pthread_mutex_lock(lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
static void stream_latency_update_cb(pa_stream *s, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
static void success_cb(pa_stream *s, int success, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
priv->retval = success;
|
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2007-12-15 16:58:25 +00:00
|
|
|
/**
|
|
|
|
* \brief waits for a pulseaudio operation to finish, frees it and
|
|
|
|
* unlocks the mainloop
|
|
|
|
* \param op operation to wait for
|
|
|
|
* \return 1 if operation has finished normally (DONE state), 0 otherwise
|
|
|
|
*/
|
2012-03-24 15:28:38 +00:00
|
|
|
static int waitop(struct priv *priv, pa_operation *op)
|
|
|
|
{
|
2009-04-09 20:04:24 +00:00
|
|
|
if (!op) {
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2009-04-09 20:04:24 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_operation_state_t state = pa_operation_get_state(op);
|
2007-10-18 13:36:59 +00:00
|
|
|
while (state == PA_OPERATION_RUNNING) {
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_wait(priv->mainloop);
|
2007-10-18 13:36:59 +00:00
|
|
|
state = pa_operation_get_state(op);
|
|
|
|
}
|
|
|
|
pa_operation_unref(op);
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2007-10-18 13:36:59 +00:00
|
|
|
return state == PA_OPERATION_DONE;
|
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
static const struct format_map {
|
2007-10-18 13:36:59 +00:00
|
|
|
int mp_format;
|
|
|
|
pa_sample_format_t pa_format;
|
|
|
|
} format_maps[] = {
|
2014-09-23 19:04:37 +00:00
|
|
|
{AF_FORMAT_FLOAT, PA_SAMPLE_FLOAT32NE},
|
2017-06-23 19:04:43 +00:00
|
|
|
{AF_FORMAT_S32, PA_SAMPLE_S32NE},
|
|
|
|
{AF_FORMAT_S16, PA_SAMPLE_S16NE},
|
2008-05-01 16:47:54 +00:00
|
|
|
{AF_FORMAT_U8, PA_SAMPLE_U8},
|
2007-10-18 13:36:59 +00:00
|
|
|
{AF_FORMAT_UNKNOWN, 0}
|
|
|
|
};
|
|
|
|
|
2014-09-23 20:57:27 +00:00
|
|
|
static pa_encoding_t map_digital_format(int format)
|
|
|
|
{
|
|
|
|
switch (format) {
|
2019-09-22 18:22:13 +00:00
|
|
|
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
|
|
|
|
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
|
|
|
|
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
|
|
|
|
case AF_FORMAT_S_DTS: return PA_ENCODING_DTS_IEC61937;
|
|
|
|
#ifdef PA_ENCODING_DTSHD_IEC61937
|
|
|
|
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTSHD_IEC61937;
|
|
|
|
#endif
|
2014-09-23 20:57:27 +00:00
|
|
|
#ifdef PA_ENCODING_MPEG2_AAC_IEC61937
|
2019-09-22 18:22:13 +00:00
|
|
|
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
|
|
|
|
#endif
|
|
|
|
#ifdef PA_ENCODING_TRUEHD_IEC61937
|
|
|
|
case AF_FORMAT_S_TRUEHD: return PA_ENCODING_TRUEHD_IEC61937;
|
2014-09-23 20:57:27 +00:00
|
|
|
#endif
|
|
|
|
default:
|
2015-06-26 21:06:37 +00:00
|
|
|
if (af_fmt_is_spdif(format))
|
2014-09-23 20:57:27 +00:00
|
|
|
return PA_ENCODING_ANY;
|
|
|
|
return PA_ENCODING_PCM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-06 21:36:00 +00:00
|
|
|
static const int speaker_map[][2] = {
|
|
|
|
{PA_CHANNEL_POSITION_FRONT_LEFT, MP_SPEAKER_ID_FL},
|
|
|
|
{PA_CHANNEL_POSITION_FRONT_RIGHT, MP_SPEAKER_ID_FR},
|
|
|
|
{PA_CHANNEL_POSITION_FRONT_CENTER, MP_SPEAKER_ID_FC},
|
|
|
|
{PA_CHANNEL_POSITION_REAR_CENTER, MP_SPEAKER_ID_BC},
|
|
|
|
{PA_CHANNEL_POSITION_REAR_LEFT, MP_SPEAKER_ID_BL},
|
|
|
|
{PA_CHANNEL_POSITION_REAR_RIGHT, MP_SPEAKER_ID_BR},
|
|
|
|
{PA_CHANNEL_POSITION_LFE, MP_SPEAKER_ID_LFE},
|
|
|
|
{PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, MP_SPEAKER_ID_FLC},
|
|
|
|
{PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, MP_SPEAKER_ID_FRC},
|
|
|
|
{PA_CHANNEL_POSITION_SIDE_LEFT, MP_SPEAKER_ID_SL},
|
|
|
|
{PA_CHANNEL_POSITION_SIDE_RIGHT, MP_SPEAKER_ID_SR},
|
|
|
|
{PA_CHANNEL_POSITION_TOP_CENTER, MP_SPEAKER_ID_TC},
|
|
|
|
{PA_CHANNEL_POSITION_TOP_FRONT_LEFT, MP_SPEAKER_ID_TFL},
|
|
|
|
{PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, MP_SPEAKER_ID_TFR},
|
|
|
|
{PA_CHANNEL_POSITION_TOP_FRONT_CENTER, MP_SPEAKER_ID_TFC},
|
|
|
|
{PA_CHANNEL_POSITION_TOP_REAR_LEFT, MP_SPEAKER_ID_TBL},
|
|
|
|
{PA_CHANNEL_POSITION_TOP_REAR_RIGHT, MP_SPEAKER_ID_TBR},
|
|
|
|
{PA_CHANNEL_POSITION_TOP_REAR_CENTER, MP_SPEAKER_ID_TBC},
|
|
|
|
{PA_CHANNEL_POSITION_INVALID, -1}
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool chmap_pa_from_mp(pa_channel_map *dst, struct mp_chmap *src)
|
|
|
|
{
|
|
|
|
if (src->num > PA_CHANNELS_MAX)
|
|
|
|
return false;
|
|
|
|
dst->channels = src->num;
|
2013-10-31 17:14:10 +00:00
|
|
|
if (mp_chmap_equals(src, &(const struct mp_chmap)MP_CHMAP_INIT_MONO)) {
|
|
|
|
dst->map[0] = PA_CHANNEL_POSITION_MONO;
|
|
|
|
return true;
|
|
|
|
}
|
2013-04-06 21:36:00 +00:00
|
|
|
for (int n = 0; n < src->num; n++) {
|
|
|
|
int mp_speaker = src->speaker[n];
|
|
|
|
int pa_speaker = PA_CHANNEL_POSITION_INVALID;
|
|
|
|
for (int i = 0; speaker_map[i][1] != -1; i++) {
|
|
|
|
if (speaker_map[i][1] == mp_speaker) {
|
|
|
|
pa_speaker = speaker_map[i][0];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pa_speaker == PA_CHANNEL_POSITION_INVALID)
|
|
|
|
return false;
|
|
|
|
dst->map[n] = pa_speaker;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-05-09 16:06:26 +00:00
|
|
|
static bool select_chmap(struct ao *ao, pa_channel_map *dst)
|
|
|
|
{
|
|
|
|
struct mp_chmap_sel sel = {0};
|
|
|
|
for (int n = 0; speaker_map[n][1] != -1; n++)
|
|
|
|
mp_chmap_sel_add_speaker(&sel, speaker_map[n][1]);
|
|
|
|
return ao_chmap_sel_adjust(ao, &sel, &ao->channels) &&
|
|
|
|
chmap_pa_from_mp(dst, &ao->channels);
|
|
|
|
}
|
|
|
|
|
2014-03-08 23:49:39 +00:00
|
|
|
static void drain(struct ao *ao)
|
2012-03-24 15:28:38 +00:00
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
2014-03-08 23:49:39 +00:00
|
|
|
if (priv->stream) {
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
waitop(priv, pa_stream_drain(priv->stream, success_cb, ao));
|
|
|
|
}
|
2014-03-08 23:49:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void uninit(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
2012-03-24 15:28:38 +00:00
|
|
|
|
|
|
|
if (priv->mainloop)
|
|
|
|
pa_threaded_mainloop_stop(priv->mainloop);
|
|
|
|
|
|
|
|
if (priv->stream) {
|
|
|
|
pa_stream_disconnect(priv->stream);
|
|
|
|
pa_stream_unref(priv->stream);
|
|
|
|
priv->stream = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->context) {
|
|
|
|
pa_context_disconnect(priv->context);
|
|
|
|
pa_context_unref(priv->context);
|
|
|
|
priv->context = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->mainloop) {
|
|
|
|
pa_threaded_mainloop_free(priv->mainloop);
|
|
|
|
priv->mainloop = NULL;
|
|
|
|
}
|
2014-05-29 21:56:48 +00:00
|
|
|
|
|
|
|
pthread_cond_destroy(&priv->wakeup);
|
|
|
|
pthread_mutex_destroy(&priv->wakeup_lock);
|
2012-03-24 15:28:38 +00:00
|
|
|
}
|
|
|
|
|
2014-10-10 16:29:25 +00:00
|
|
|
static int pa_init_boilerplate(struct ao *ao)
|
2012-03-24 15:28:38 +00:00
|
|
|
{
|
2013-07-21 20:13:11 +00:00
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
char *host = priv->cfg_host && priv->cfg_host[0] ? priv->cfg_host : NULL;
|
2014-10-10 16:29:25 +00:00
|
|
|
bool locked = false;
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2014-05-29 21:56:48 +00:00
|
|
|
pthread_mutex_init(&priv->wakeup_lock, NULL);
|
|
|
|
pthread_cond_init(&priv->wakeup, NULL);
|
|
|
|
|
2013-05-09 15:34:17 +00:00
|
|
|
if (!(priv->mainloop = pa_threaded_mainloop_new())) {
|
2013-08-22 21:12:35 +00:00
|
|
|
MP_ERR(ao, "Failed to allocate main loop\n");
|
2013-05-09 15:34:17 +00:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2014-10-10 16:29:25 +00:00
|
|
|
if (pa_threaded_mainloop_start(priv->mainloop) < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
locked = true;
|
|
|
|
|
2013-05-09 15:34:17 +00:00
|
|
|
if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api(
|
2014-11-07 14:54:35 +00:00
|
|
|
priv->mainloop), ao->client_name)))
|
2014-10-10 16:29:25 +00:00
|
|
|
{
|
2013-08-22 21:12:35 +00:00
|
|
|
MP_ERR(ao, "Failed to allocate context\n");
|
2013-05-09 15:34:17 +00:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2014-09-10 21:12:07 +00:00
|
|
|
MP_VERBOSE(ao, "Library version: %s\n", pa_get_library_version());
|
|
|
|
MP_VERBOSE(ao, "Proto: %lu\n",
|
|
|
|
(long)pa_context_get_protocol_version(priv->context));
|
|
|
|
MP_VERBOSE(ao, "Server proto: %lu\n",
|
|
|
|
(long)pa_context_get_server_protocol_version(priv->context));
|
|
|
|
|
2013-05-09 15:34:17 +00:00
|
|
|
pa_context_set_state_callback(priv->context, context_state_cb, ao);
|
2015-02-12 16:00:52 +00:00
|
|
|
pa_context_set_subscribe_callback(priv->context, subscribe_cb, ao);
|
2013-05-09 15:34:17 +00:00
|
|
|
|
|
|
|
if (pa_context_connect(priv->context, host, 0, NULL) < 0)
|
|
|
|
goto fail;
|
|
|
|
|
2014-10-10 16:29:25 +00:00
|
|
|
/* Wait until the context is ready */
|
|
|
|
while (1) {
|
|
|
|
int state = pa_context_get_state(priv->context);
|
|
|
|
if (state == PA_CONTEXT_READY)
|
|
|
|
break;
|
|
|
|
if (!PA_CONTEXT_IS_GOOD(state))
|
|
|
|
goto fail;
|
|
|
|
pa_threaded_mainloop_wait(priv->mainloop);
|
|
|
|
}
|
2013-05-09 15:34:17 +00:00
|
|
|
|
2014-10-10 16:29:25 +00:00
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
|
|
|
return 0;
|
2013-05-09 15:34:17 +00:00
|
|
|
|
2014-10-10 16:29:25 +00:00
|
|
|
fail:
|
|
|
|
if (locked)
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2013-05-09 15:34:17 +00:00
|
|
|
|
2014-10-10 16:29:25 +00:00
|
|
|
if (priv->context) {
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
if (!(pa_context_errno(priv->context) == PA_ERR_CONNECTIONREFUSED
|
|
|
|
&& ao->probing))
|
|
|
|
GENERIC_ERR_MSG("Init failed");
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
|
|
|
}
|
|
|
|
uninit(ao);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-01-05 18:34:34 +00:00
|
|
|
static bool set_format(struct ao *ao, pa_format_info *format)
|
2014-10-10 16:29:25 +00:00
|
|
|
{
|
2013-11-10 22:15:02 +00:00
|
|
|
ao->format = af_fmt_from_planar(ao->format);
|
|
|
|
|
2014-09-23 20:57:27 +00:00
|
|
|
format->encoding = map_digital_format(ao->format);
|
|
|
|
if (format->encoding == PA_ENCODING_PCM) {
|
|
|
|
const struct format_map *fmt_map = format_maps;
|
|
|
|
|
|
|
|
while (fmt_map->mp_format != ao->format) {
|
|
|
|
if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) {
|
|
|
|
MP_VERBOSE(ao, "Unsupported format, using default\n");
|
|
|
|
fmt_map = format_maps;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fmt_map++;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
2014-09-23 20:57:27 +00:00
|
|
|
ao->format = fmt_map->mp_format;
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2014-09-23 20:57:27 +00:00
|
|
|
pa_format_info_set_sample_format(format, fmt_map->pa_format);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2016-01-05 18:34:34 +00:00
|
|
|
struct pa_channel_map map;
|
|
|
|
|
2013-05-09 16:06:26 +00:00
|
|
|
if (!select_chmap(ao, &map))
|
2016-01-05 18:34:34 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
pa_format_info_set_rate(format, ao->samplerate);
|
|
|
|
pa_format_info_set_channels(format, ao->channels.num);
|
|
|
|
pa_format_info_set_channel_map(format, &map);
|
|
|
|
|
2016-01-05 18:37:08 +00:00
|
|
|
return ao->samplerate < PA_RATE_MAX && pa_format_info_valid(format);
|
2016-01-05 18:34:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int init(struct ao *ao)
|
|
|
|
{
|
|
|
|
pa_proplist *proplist = NULL;
|
|
|
|
pa_format_info *format = NULL;
|
|
|
|
struct priv *priv = ao->priv;
|
2017-04-23 15:51:55 +00:00
|
|
|
char *sink = ao->device;
|
2016-01-05 18:34:34 +00:00
|
|
|
|
|
|
|
if (pa_init_boilerplate(ao) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
2013-04-05 21:06:22 +00:00
|
|
|
|
2013-09-20 03:58:08 +00:00
|
|
|
if (!(proplist = pa_proplist_new())) {
|
|
|
|
MP_ERR(ao, "Failed to allocate proplist\n");
|
|
|
|
goto unlock_and_fail;
|
|
|
|
}
|
2014-11-07 14:54:35 +00:00
|
|
|
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME, ao->client_name);
|
2013-09-20 03:58:08 +00:00
|
|
|
|
2016-01-05 18:34:34 +00:00
|
|
|
if (!(format = pa_format_info_new()))
|
|
|
|
goto unlock_and_fail;
|
2014-09-23 20:57:27 +00:00
|
|
|
|
2016-01-05 18:34:34 +00:00
|
|
|
if (!set_format(ao, format)) {
|
ao_pulse: attempt to fall back to an arbitrary sample format
Normally, PulseAudio accepts any combination of sample format, sample
rate, channel count/map. Sometimes it does not. For example, the channel
rate or channel count have fixed maximum values. We should not fail
fatally in such cases, but attempt to fall back to a working format.
We could just send pass an "unset" format to Pulse, but this is not too
attractive. Pulse could use a format which we do not support, and also
doing so much for an obscure corner case is not reasonable. So just pick
a format that is very likely supported.
This still could fail at runtime (the stream could fail instead of going
to the ready state), but this sounds also too complicated. In
particular, it doesn't look like pulse will tell us the cause of the
stream failure. (Or maybe it does - but I didn't find anything.)
Last but not least, our fallback could be less dumb, and e.g. try to fix
only one of samplerate or channel count first to reduce the loss, but
this is also not particularly worthy the effort.
Fixes #2654.
2016-01-05 18:52:05 +00:00
|
|
|
ao->channels = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
|
|
|
|
ao->samplerate = 48000;
|
|
|
|
ao->format = AF_FORMAT_FLOAT;
|
|
|
|
if (!set_format(ao, format)) {
|
|
|
|
MP_ERR(ao, "Invalid audio format\n");
|
|
|
|
goto unlock_and_fail;
|
|
|
|
}
|
2014-09-23 20:57:27 +00:00
|
|
|
}
|
2014-09-23 20:47:14 +00:00
|
|
|
|
|
|
|
if (!(priv->stream = pa_stream_new_extended(priv->context, "audio stream",
|
|
|
|
&format, 1, proplist)))
|
2007-10-18 13:36:59 +00:00
|
|
|
goto unlock_and_fail;
|
|
|
|
|
2015-01-25 00:23:35 +00:00
|
|
|
pa_format_info_free(format);
|
|
|
|
format = NULL;
|
|
|
|
|
2013-09-20 03:58:08 +00:00
|
|
|
pa_proplist_free(proplist);
|
|
|
|
proplist = NULL;
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_stream_set_state_callback(priv->stream, stream_state_cb, ao);
|
|
|
|
pa_stream_set_write_callback(priv->stream, stream_request_cb, ao);
|
|
|
|
pa_stream_set_latency_update_callback(priv->stream,
|
|
|
|
stream_latency_update_cb, ao);
|
2018-08-21 00:34:23 +00:00
|
|
|
uint32_t buf_size = ao->samplerate * (priv->cfg_buffer / 1000.0) *
|
|
|
|
af_fmt_to_bytes(ao->format) * ao->channels.num;
|
2012-03-25 19:58:48 +00:00
|
|
|
pa_buffer_attr bufattr = {
|
|
|
|
.maxlength = -1,
|
2018-08-21 00:34:23 +00:00
|
|
|
.tlength = buf_size > 0 ? buf_size : -1,
|
2012-03-25 19:58:48 +00:00
|
|
|
.prebuf = -1,
|
|
|
|
.minreq = -1,
|
|
|
|
.fragsize = -1,
|
|
|
|
};
|
2014-07-26 21:19:14 +00:00
|
|
|
|
2014-10-04 21:29:52 +00:00
|
|
|
int flags = PA_STREAM_NOT_MONOTONIC;
|
2014-07-26 21:19:14 +00:00
|
|
|
if (!priv->cfg_latency_hacks)
|
|
|
|
flags |= PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE;
|
|
|
|
|
2012-03-25 19:58:48 +00:00
|
|
|
if (pa_stream_connect_playback(priv->stream, sink, &bufattr,
|
2014-07-26 21:19:14 +00:00
|
|
|
flags, NULL, NULL) < 0)
|
2007-10-18 13:36:59 +00:00
|
|
|
goto unlock_and_fail;
|
|
|
|
|
|
|
|
/* Wait until the stream is ready */
|
2014-09-23 20:47:32 +00:00
|
|
|
while (1) {
|
|
|
|
int state = pa_stream_get_state(priv->stream);
|
|
|
|
if (state == PA_STREAM_READY)
|
|
|
|
break;
|
|
|
|
if (!PA_STREAM_IS_GOOD(state))
|
|
|
|
goto unlock_and_fail;
|
|
|
|
pa_threaded_mainloop_wait(priv->mainloop);
|
|
|
|
}
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2019-05-05 04:53:41 +00:00
|
|
|
if (pa_stream_is_suspended(priv->stream) && !priv->cfg_allow_suspended) {
|
2014-10-04 21:29:52 +00:00
|
|
|
MP_ERR(ao, "The stream is suspended. Bailing out.\n");
|
|
|
|
goto unlock_and_fail;
|
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
|
|
|
return 0;
|
2007-10-18 13:36:59 +00:00
|
|
|
|
|
|
|
unlock_and_fail:
|
2014-10-10 16:29:25 +00:00
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2007-10-18 13:36:59 +00:00
|
|
|
|
2014-09-23 20:47:14 +00:00
|
|
|
if (format)
|
|
|
|
pa_format_info_free(format);
|
2013-09-20 03:58:08 +00:00
|
|
|
|
|
|
|
if (proplist)
|
|
|
|
pa_proplist_free(proplist);
|
|
|
|
|
2014-03-08 23:49:39 +00:00
|
|
|
uninit(ao);
|
2012-03-24 15:28:38 +00:00
|
|
|
return -1;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
static void cork(struct ao *ao, bool pause)
|
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
priv->retval = 0;
|
|
|
|
if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) ||
|
|
|
|
!priv->retval)
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_stream_cork() failed");
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
// Play the specified data to the pulseaudio server
|
2013-11-10 22:24:21 +00:00
|
|
|
static int play(struct ao *ao, void **data, int samples, int flags)
|
2012-03-24 15:28:38 +00:00
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
2013-11-10 22:24:21 +00:00
|
|
|
if (pa_stream_write(priv->stream, data[0], samples * ao->sstride, NULL, 0,
|
2012-03-24 15:28:38 +00:00
|
|
|
PA_SEEK_RELATIVE) < 0) {
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_stream_write() failed");
|
2013-11-10 22:24:21 +00:00
|
|
|
samples = -1;
|
2007-12-15 17:07:40 +00:00
|
|
|
}
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
if (flags & AOPLAY_FINAL_CHUNK) {
|
|
|
|
// Force start in case the stream was too short for prebuf
|
|
|
|
pa_operation *op = pa_stream_trigger(priv->stream, NULL, NULL);
|
|
|
|
pa_operation_unref(op);
|
|
|
|
}
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2013-11-10 22:24:21 +00:00
|
|
|
return samples;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
// Reset the audio stream, i.e. flush the playback buffer on the server side
|
|
|
|
static void reset(struct ao *ao)
|
|
|
|
{
|
2012-03-26 00:47:44 +00:00
|
|
|
// pa_stream_flush() works badly if not corked
|
|
|
|
cork(ao, true);
|
2012-03-24 15:28:38 +00:00
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
priv->retval = 0;
|
|
|
|
if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) ||
|
|
|
|
!priv->retval)
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_stream_flush() failed");
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
cork(ao, false);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
// Pause the audio stream by corking it on the server
|
|
|
|
static void pause(struct ao *ao)
|
|
|
|
{
|
|
|
|
cork(ao, true);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
// Resume the audio stream by uncorking it on the server
|
|
|
|
static void resume(struct ao *ao)
|
|
|
|
{
|
|
|
|
cork(ao, false);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2013-11-10 22:24:21 +00:00
|
|
|
// Return number of samples that may be written to the server without blocking
|
2012-03-24 15:28:38 +00:00
|
|
|
static int get_space(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
size_t space = pa_stream_writable_size(priv->stream);
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2013-11-10 22:24:21 +00:00
|
|
|
return space / ao->sstride;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2014-11-09 10:45:04 +00:00
|
|
|
static double get_delay_hackfixed(struct ao *ao)
|
2012-03-24 15:28:38 +00:00
|
|
|
{
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
/* This code basically does what pa_stream_get_latency() _should_
|
|
|
|
* do, but doesn't due to multiple known bugs in PulseAudio (at
|
|
|
|
* PulseAudio version 2.1). In particular, the timing interpolation
|
|
|
|
* mode (PA_STREAM_INTERPOLATE_TIMING) can return completely bogus
|
|
|
|
* values, and the non-interpolating code has a bug causing too
|
|
|
|
* large results at end of stream (so a stream never seems to finish).
|
|
|
|
* This code can still return wrong values in some cases due to known
|
|
|
|
* PulseAudio bugs that can not be worked around on the client side.
|
|
|
|
*
|
|
|
|
* We always query the server for latest timing info. This may take
|
|
|
|
* too long to work well with remote audio servers, but at least
|
|
|
|
* this should be enough to fix the normal local playback case.
|
|
|
|
*/
|
2012-03-24 15:28:38 +00:00
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
if (!waitop(priv, pa_stream_update_timing_info(priv->stream, NULL, NULL))) {
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_stream_update_timing_info() failed");
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
const pa_timing_info *ti = pa_stream_get_timing_info(priv->stream);
|
|
|
|
if (!ti) {
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_stream_get_timing_info() failed");
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const struct pa_sample_spec *ss = pa_stream_get_sample_spec(priv->stream);
|
|
|
|
if (!ss) {
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_stream_get_sample_spec() failed");
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
return 0;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
// data left in PulseAudio's main buffers (not written to sink yet)
|
|
|
|
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
|
|
|
|
// since this info may be from a while ago, playback has progressed since
|
|
|
|
latency -= ti->transport_usec;
|
|
|
|
// data already moved from buffers to sink, but not played yet
|
|
|
|
int64_t sink_latency = ti->sink_usec;
|
|
|
|
if (!ti->playing)
|
|
|
|
/* At the end of a stream, part of the data "left" in the sink may
|
|
|
|
* be padding silence after the end; that should be subtracted to
|
|
|
|
* get the amount of real audio from our stream. This adjustment
|
|
|
|
* is missing from Pulseaudio's own get_latency calculations
|
|
|
|
* (as of PulseAudio 2.1). */
|
|
|
|
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
|
|
|
|
if (sink_latency > 0)
|
|
|
|
latency += sink_latency;
|
|
|
|
if (latency < 0)
|
|
|
|
latency = 0;
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
ao_pulse: work around PulseAudio timing bugs
Work around PulseAudio bugs more effectively. In particular, this
should avoid two issues: playback never finishing at end of file /
segment due to PulseAudio always claiming there's still time before
audio playback reaches the end, and jerky playback especially after
seeking due to bogus output from PulseAudio's timing interpolation
code.
This time, I looked into the PulseAudio code itself and analyzed the
bugs causing problems. Fortunately, two of the serious ones can be
worked around in client code. Write a new get_delay() implementation
doing that, and remove some of the previous workarounds which are now
unnecessary. Also add a pa_stream_trigger() call to ensure playback of
files shorter than prebuf value starts (btw doing that by setting a
low prebuf hits yet another PulseAudio bug, even if you then write the
whole file in one call).
There are still a couple of known PulseAudio bugs that can not be
worked around in client code. Especially, bug 4 below can cause issues
when pausing.
Below is a copy of a message I sent to the pulseaudio-discuss mailing
list, describing some of the PulseAudio bugs:
==================================================
A lot of mplayer2 users with PulseAudio have experienced problems. I
investigated some of those and confirmed that they are caused by
PulseAudio. There are quite a few distinct PulseAudio bugs; some are
analyzed below. Overall, however, I wonder why there are so many fairly
obvious bugs in a widely used piece of software. Is there no
maintenance? Or do people not test it? Some of the bugs are probably
less obvious if you request low latency (though they're not specific to
higher-latency case); do people test the low-latency case only?
1. The timing interpolation functionality can return completely bogus
values for playback position and latency, especially after seeking
(mplayer2 does cork / flush / uncork, as flushing alone does not seem to
remove data already in sink). I've seen quickly repeated seeks report
over 10 second latency, when there aren't any buffers anywhere that big.
I have not investigated the exact cause. Instead I disabled
interpolation and added code to always call
pa_stream_update_timing_info(). (I assume that always waiting for this
to complete, instead of doing custom interpolation, may give bad
performance if it queries a remote server. But at least it works better
locally.)
2. Position/latency reporting is wrong at the end of a stream (after the
lack of more data triggers underflow status). As a result mplayer2 never
ends the playback of a file, as it's waiting forever for audio to finish
playing. The reason for this is that the calculations in PulseAudio add
the whole length of data in the sink to the current latency (subtract
from position), even if the sink does not contain that much data *from
this stream* in underflow conditions. I was able to work around this bug
by calculating latency from pa_timing_info data myself as follows
(ti=pa_timing_info):
int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
latency -= ti->transport_usec;
int64_t sink_latency = ti->sink_usec;
if (!ti->playing)
// this part is missing from PulseAudio itself
sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
if (sink_latency > 0)
latency += sink_latency;
if (latency < 0)
latency = 0;
However, this still doesn't always work due to the next bug.
3. The since_underrun field in pa_timing_info is wrong if PulseAudio is
resampling the stream. As a result, the above code indicated that the
playback of a 0.1 second 8-bit mono file would take about 0.5 seconds.
This bug is in pa_sink_input_peek(). The problematic parts are:
ilength = pa_resampler_request(i->thread_info.resampler, slength);
...
if (ilength > block_size_max_sink_input)
ilength = block_size_max_sink_input;
...
pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
...
i->thread_info.underrun_for += ilength;
This is measuring audio in two different units, bytes for
resampled-to-sink (slength) and original stream (ilength). However, the
block_size_max_sink_input test only adjusts ilength; after that the
values may be out of sync. Thus underrun_for is incremented by less than
it should be to match the slength value used in pa_memblockq_seek.
4. Stream rewind functionality breaks if the sink is suspended (while
the stream is corked). Thus, if you pause for more than 5 seconds
without other audio playing, things are broken after that. The most
obvious symptom is that playback can continue for a significant time
after corking. This is caused by sink_input and sink getting out of
sync. First, after uncorking a stream on a suspended sink,
pa_sink_input_request_rewind() is called while the sink is still in
suspended state. This sets sink_input->thread_info.rewrite_nbytes to -1
and calls pa_sink_request_rewind(). However, the sink ignores rewind
requests while suspended. Thus this particular rewind does nothing. The
problem is that rewrite_nbytes is left at -1. Further calls to
pa_sink_input_request_rewind() do nothing because "nbytes =
PA_MAX(i->thread_info.rewrite_nbytes, nbytes);" sets nbytes to -1, and
the call to pa_sink_request_rewind() is under "if (nbytes != (size_t)
-1) {". Usually, after a sink responds to a rewind request,
rewrite_bytes is reset in pa_sink_input_process_rewind(), but this
doesn't happen if the sink ever ignores one request. This broken state
can be resolved if pa_sink_input_process_rewind() is called due to a
rewind triggered by _another_ stream.
There were more bugs, but I'll leave those for later.
2012-07-29 17:56:31 +00:00
|
|
|
return latency / 1e6;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2014-11-09 10:45:04 +00:00
|
|
|
static double get_delay_pulse(struct ao *ao)
|
2014-07-26 21:19:14 +00:00
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
pa_usec_t latency = (pa_usec_t) -1;
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
while (pa_stream_get_latency(priv->stream, &latency, NULL) < 0) {
|
|
|
|
if (pa_context_errno(priv->context) != PA_ERR_NODATA) {
|
|
|
|
GENERIC_ERR_MSG("pa_stream_get_latency() failed");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* Wait until latency data is available again */
|
|
|
|
pa_threaded_mainloop_wait(priv->mainloop);
|
|
|
|
}
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
|
|
|
return latency == (pa_usec_t) -1 ? 0 : latency / 1000000.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the current latency in seconds
|
2014-11-09 10:45:04 +00:00
|
|
|
static double get_delay(struct ao *ao)
|
2014-07-26 21:19:14 +00:00
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
if (priv->cfg_latency_hacks) {
|
|
|
|
return get_delay_hackfixed(ao);
|
|
|
|
} else {
|
|
|
|
return get_delay_pulse(ao);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
/* A callback function that is called when the
|
2007-10-18 13:36:59 +00:00
|
|
|
* pa_context_get_sink_input_info() operation completes. Saves the
|
2012-03-24 15:28:38 +00:00
|
|
|
* volume field of the specified structure to the global variable volume.
|
|
|
|
*/
|
|
|
|
static void info_func(struct pa_context *c, const struct pa_sink_input_info *i,
|
|
|
|
int is_last, void *userdata)
|
|
|
|
{
|
|
|
|
struct ao *ao = userdata;
|
|
|
|
struct priv *priv = ao->priv;
|
2007-10-18 13:36:59 +00:00
|
|
|
if (is_last < 0) {
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("Failed to get sink input info");
|
2007-10-18 13:36:59 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!i)
|
|
|
|
return;
|
2012-01-17 06:55:04 +00:00
|
|
|
priv->pi = *i;
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
2012-04-07 13:26:56 +00:00
|
|
|
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
2012-03-24 15:28:38 +00:00
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
2007-10-18 13:36:59 +00:00
|
|
|
switch (cmd) {
|
2012-01-17 06:55:04 +00:00
|
|
|
case AOCONTROL_GET_MUTE:
|
2012-03-24 15:28:38 +00:00
|
|
|
case AOCONTROL_GET_VOLUME: {
|
|
|
|
uint32_t devidx = pa_stream_get_index(priv->stream);
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
if (!waitop(priv, pa_context_get_sink_input_info(priv->context, devidx,
|
|
|
|
info_func, ao))) {
|
2014-09-08 15:19:53 +00:00
|
|
|
GENERIC_ERR_MSG("pa_context_get_sink_input_info() failed");
|
2012-03-24 15:28:38 +00:00
|
|
|
return CONTROL_ERROR;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
2012-01-17 06:55:04 +00:00
|
|
|
// Warning: some information in pi might be unaccessible, because
|
|
|
|
// we naively copied the struct, without updating pointers etc.
|
|
|
|
// Pointers might point to invalid data, accessors might fail.
|
|
|
|
if (cmd == AOCONTROL_GET_VOLUME) {
|
|
|
|
ao_control_vol_t *vol = arg;
|
|
|
|
if (priv->pi.volume.channels != 2)
|
|
|
|
vol->left = vol->right =
|
2012-09-18 19:42:09 +00:00
|
|
|
VOL_PA2MP(pa_cvolume_avg(&priv->pi.volume));
|
2012-01-17 06:55:04 +00:00
|
|
|
else {
|
2012-09-18 19:42:09 +00:00
|
|
|
vol->left = VOL_PA2MP(priv->pi.volume.values[0]);
|
|
|
|
vol->right = VOL_PA2MP(priv->pi.volume.values[1]);
|
2012-01-17 06:55:04 +00:00
|
|
|
}
|
|
|
|
} else if (cmd == AOCONTROL_GET_MUTE) {
|
|
|
|
bool *mute = arg;
|
|
|
|
*mute = priv->pi.mute;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
2012-03-24 15:28:38 +00:00
|
|
|
return CONTROL_OK;
|
|
|
|
}
|
2012-01-17 06:55:04 +00:00
|
|
|
|
|
|
|
case AOCONTROL_SET_MUTE:
|
2012-03-24 15:28:38 +00:00
|
|
|
case AOCONTROL_SET_VOLUME: {
|
|
|
|
pa_operation *o;
|
2012-01-17 06:55:04 +00:00
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
2012-01-17 06:55:04 +00:00
|
|
|
uint32_t stream_index = pa_stream_get_index(priv->stream);
|
|
|
|
if (cmd == AOCONTROL_SET_VOLUME) {
|
|
|
|
const ao_control_vol_t *vol = arg;
|
|
|
|
struct pa_cvolume volume;
|
|
|
|
|
2013-04-05 21:06:22 +00:00
|
|
|
pa_cvolume_reset(&volume, ao->channels.num);
|
2012-01-17 06:55:04 +00:00
|
|
|
if (volume.channels != 2)
|
2012-09-18 19:42:09 +00:00
|
|
|
pa_cvolume_set(&volume, volume.channels, VOL_MP2PA(vol->left));
|
2012-01-17 06:55:04 +00:00
|
|
|
else {
|
2012-09-18 19:42:09 +00:00
|
|
|
volume.values[0] = VOL_MP2PA(vol->left);
|
|
|
|
volume.values[1] = VOL_MP2PA(vol->right);
|
2012-01-17 06:55:04 +00:00
|
|
|
}
|
|
|
|
o = pa_context_set_sink_input_volume(priv->context, stream_index,
|
|
|
|
&volume, NULL, NULL);
|
|
|
|
if (!o) {
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_context_set_sink_input_volume() failed");
|
2012-01-17 06:55:04 +00:00
|
|
|
return CONTROL_ERROR;
|
|
|
|
}
|
|
|
|
} else if (cmd == AOCONTROL_SET_MUTE) {
|
|
|
|
const bool *mute = arg;
|
|
|
|
o = pa_context_set_sink_input_mute(priv->context, stream_index,
|
|
|
|
*mute, NULL, NULL);
|
|
|
|
if (!o) {
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
2013-08-22 21:12:35 +00:00
|
|
|
GENERIC_ERR_MSG("pa_context_set_sink_input_mute() failed");
|
2012-01-17 06:55:04 +00:00
|
|
|
return CONTROL_ERROR;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
abort();
|
2012-03-24 15:28:38 +00:00
|
|
|
/* We don't wait for completion here */
|
|
|
|
pa_operation_unref(o);
|
|
|
|
pa_threaded_mainloop_unlock(priv->mainloop);
|
|
|
|
return CONTROL_OK;
|
|
|
|
}
|
2013-11-09 23:49:13 +00:00
|
|
|
|
|
|
|
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
|
|
|
char *title = (char *)arg;
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
if (!waitop(priv, pa_stream_set_name(priv->stream, title,
|
|
|
|
success_cb, ao)))
|
|
|
|
{
|
|
|
|
GENERIC_ERR_MSG("pa_stream_set_name() failed");
|
|
|
|
return CONTROL_ERROR;
|
|
|
|
}
|
|
|
|
return CONTROL_OK;
|
|
|
|
}
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
default:
|
|
|
|
return CONTROL_UNKNOWN;
|
2007-10-18 13:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
2012-03-24 15:28:38 +00:00
|
|
|
|
2014-10-10 16:33:55 +00:00
|
|
|
struct sink_cb_ctx {
|
|
|
|
struct ao *ao;
|
|
|
|
struct ao_device_list *list;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *ud)
|
|
|
|
{
|
|
|
|
struct sink_cb_ctx *ctx = ud;
|
|
|
|
struct priv *priv = ctx->ao->priv;
|
|
|
|
|
|
|
|
if (eol) {
|
|
|
|
pa_threaded_mainloop_signal(priv->mainloop, 0); // wakeup waitop()
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ao_device_desc entry = {.name = i->name, .desc = i->description};
|
|
|
|
ao_device_list_add(ctx->list, ctx->ao, &entry);
|
|
|
|
}
|
|
|
|
|
2015-02-12 16:00:52 +00:00
|
|
|
static int hotplug_init(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
if (pa_init_boilerplate(ao) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
waitop(priv, pa_context_subscribe(priv->context, PA_SUBSCRIPTION_MASK_SINK,
|
|
|
|
context_success_cb, ao));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-10 16:33:55 +00:00
|
|
|
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
|
|
|
{
|
|
|
|
struct priv *priv = ao->priv;
|
|
|
|
struct sink_cb_ctx ctx = {ao, list};
|
|
|
|
|
|
|
|
pa_threaded_mainloop_lock(priv->mainloop);
|
|
|
|
waitop(priv, pa_context_get_sink_info_list(priv->context, sink_info_cb, &ctx));
|
|
|
|
}
|
|
|
|
|
2015-02-12 16:00:52 +00:00
|
|
|
static void hotplug_uninit(struct ao *ao)
|
|
|
|
{
|
|
|
|
uninit(ao);
|
|
|
|
}
|
2014-10-10 16:33:55 +00:00
|
|
|
|
2013-07-21 20:13:11 +00:00
|
|
|
#define OPT_BASE_STRUCT struct priv
|
|
|
|
|
2012-03-24 15:28:38 +00:00
|
|
|
const struct ao_driver audio_out_pulse = {
|
2013-10-23 17:07:27 +00:00
|
|
|
.description = "PulseAudio audio output",
|
|
|
|
.name = "pulse",
|
2012-03-24 15:28:38 +00:00
|
|
|
.control = control,
|
|
|
|
.init = init,
|
|
|
|
.uninit = uninit,
|
|
|
|
.reset = reset,
|
|
|
|
.get_space = get_space,
|
|
|
|
.play = play,
|
|
|
|
.get_delay = get_delay,
|
|
|
|
.pause = pause,
|
|
|
|
.resume = resume,
|
2014-03-08 23:49:39 +00:00
|
|
|
.drain = drain,
|
2014-07-22 17:22:31 +00:00
|
|
|
.wait = wait_audio,
|
2014-05-29 21:56:48 +00:00
|
|
|
.wakeup = wakeup,
|
2015-02-12 16:00:52 +00:00
|
|
|
.hotplug_init = hotplug_init,
|
|
|
|
.hotplug_uninit = hotplug_uninit,
|
2014-10-10 16:33:55 +00:00
|
|
|
.list_devs = list_devs,
|
2013-07-21 20:13:11 +00:00
|
|
|
.priv_size = sizeof(struct priv),
|
2014-01-07 22:50:22 +00:00
|
|
|
.priv_defaults = &(const struct priv) {
|
2018-04-05 14:35:53 +00:00
|
|
|
.cfg_buffer = 100,
|
2014-01-07 22:50:22 +00:00
|
|
|
},
|
2013-07-21 20:13:11 +00:00
|
|
|
.options = (const struct m_option[]) {
|
|
|
|
OPT_STRING("host", cfg_host, 0),
|
2014-10-06 19:46:44 +00:00
|
|
|
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
|
2014-07-26 21:19:14 +00:00
|
|
|
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
|
2019-05-05 04:53:41 +00:00
|
|
|
OPT_FLAG("allow-suspended", cfg_allow_suspended, 0),
|
2013-07-21 20:13:11 +00:00
|
|
|
{0}
|
|
|
|
},
|
2016-11-25 20:00:39 +00:00
|
|
|
.options_prefix = "pulse",
|
2012-03-24 15:28:38 +00:00
|
|
|
};
|