rdesktop/rdpsnd_pulse.c

1476 lines
34 KiB
C
Raw Permalink Normal View History

/* -*- c-basic-offset: 8 -*-
rdesktop: A Remote Desktop Protocol client.
Sound Channel Process Functions - PulseAudio
Copyright (C) Krupen'ko Nikita <krnekit@gmail.com> 2010
Copyright (C) Henrik Andersson <hean01@cendio.com> 2017
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "rdesktop.h"
#include "rdpsnd.h"
#include "rdpsnd_dsp.h"
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <sys/time.h>
#include <pulse/version.h>
#ifndef PA_CHECK_VERSION
#define PA_CHECK_VERSION(major, minor, micro) (0)
#endif
#include <pulse/thread-mainloop.h>
#if PA_CHECK_VERSION(0,9,11)
#include <pulse/proplist.h>
#endif
#include <pulse/context.h>
#include <pulse/sample.h>
#include <pulse/stream.h>
#include <pulse/error.h>
#define DEFAULTDEVICE NULL
/* Messages that may be sent from the PulseAudio thread */
enum RDPSND_PULSE_MSG_TYPE
{
2018-10-29 15:50:44 +01:00
RDPSND_PULSE_OUT_AVAIL, // Output space available in output stream
RDPSND_PULSE_IN_AVAIL, // Input data available in input stream
RDPSND_PULSE_OUT_ERR, // An error occured in the output stream
RDPSND_PULSE_IN_ERR // An error occured in the input strem
};
static pa_threaded_mainloop *mainloop;
#if PA_CHECK_VERSION(0,9,11)
static pa_proplist *proplist;
#endif
static pa_context *context;
static pa_stream *playback_stream;
static pa_stream *capture_stream;
2018-10-29 15:50:44 +01:00
static int pulse_ctl[2] = { -1, -1 }; // Pipe for comminicating with main thread
/* Streams states for the possibility of the proper reinitialization */
static RD_BOOL playback_started = False;
static RD_BOOL capture_started = False;
/* Device's parameters */
static const char *device;
static int playback_channels;
static int playback_samplerate;
static int playback_samplewidth;
static int capture_channels;
static int capture_samplerate;
static int capture_samplewidth;
/* Internal audio buffer sizes (latency) */
static const int playback_latency_part = 10; // Playback latency (in part of second)
static const int capture_latency_part = 10; // Capture latency (in part of second)
/* Capture buffer */
static void *capture_buf = NULL;
static size_t capture_buf_size = 0;
static RD_BOOL pulse_init(void);
static void pulse_deinit(void);
static RD_BOOL pulse_context_init(void);
static void pulse_context_deinit(void);
2018-10-29 15:50:44 +01:00
static RD_BOOL pulse_stream_open(pa_stream ** stream, int channels, int samplerate, int samplewidth,
pa_stream_flags_t flags);
static void pulse_stream_close(pa_stream ** stream);
static void pulse_send_msg(int fd, char message);
static RD_BOOL pulse_playback_start(void);
static RD_BOOL pulse_playback_stop(void);
static RD_BOOL pulse_playback_set_audio(int channels, int samplerate, int samplewidth);
static RD_BOOL pulse_play(void);
static RD_BOOL pulse_capture_start(void);
static RD_BOOL pulse_capture_stop(void);
static RD_BOOL pulse_capture_set_audio(int channels, int samplerate, int samplewidth);
static RD_BOOL pulse_record(void);
static RD_BOOL pulse_recover(pa_stream ** stream);
/* Callbacks for the PulseAudio events */
2018-10-29 15:50:44 +01:00
static void pulse_context_state_cb(pa_context * c, void *userdata);
static void pulse_stream_state_cb(pa_stream * p, void *userdata);
static void pulse_write_cb(pa_stream *, size_t nbytes, void *userdata);
static void pulse_read_cb(pa_stream * p, size_t nbytes, void *userdata);
2018-10-29 15:50:44 +01:00
static void pulse_cork_cb(pa_stream * p, int success, void *userdata);
static void pulse_flush_cb(pa_stream * p, int success, void *userdata);
static void pulse_update_timing_cb(pa_stream * p, int success, void *userdata);
static RD_BOOL
pulse_init(void)
{
RD_BOOL ret = False;
do
{
/* PulsaAudio mainloop thread initialization */
mainloop = pa_threaded_mainloop_new();
if (mainloop == NULL)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_init(), Error creating PulseAudio threaded mainloop");
break;
}
if (pa_threaded_mainloop_start(mainloop) != 0)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_init(), Error starting PulseAudio threaded mainloop");
break;
}
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
/* PulseAudio proplist initialization */
proplist = pa_proplist_new();
if (proplist == NULL)
{
logger(Sound, Error, "pulse_init(), Error creating PulseAudio proplist");
break;
}
if (pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "rdesktop") != 0)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_init(), Error setting option to PulseAudio proplist");
break;
}
2018-10-29 15:50:44 +01:00
#endif
if (pulse_context_init() != True)
break;
ret = True;
}
while (0);
if (ret != True)
pulse_deinit();
return ret;
}
static void
pulse_deinit(void)
{
pulse_stream_close(&capture_stream);
pulse_stream_close(&playback_stream);
pulse_context_deinit();
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
if (proplist != NULL)
{
pa_proplist_free(proplist);
proplist = NULL;
}
2018-10-29 15:50:44 +01:00
#endif
if (mainloop != NULL)
{
pa_threaded_mainloop_stop(mainloop);
pa_threaded_mainloop_free(mainloop);
mainloop = NULL;
}
}
static RD_BOOL
pulse_context_init(void)
{
pa_context_flags_t flags;
pa_context_state_t context_state;
int err;
RD_BOOL ret = False;
pa_threaded_mainloop_lock(mainloop);
do
{
/* Pipe for the control information from the audio thread */
if (pipe(pulse_ctl) != 0)
{
logger(Sound, Error, "pulse_context_init(), pipe: %s", strerror(errno));
pulse_ctl[0] = pulse_ctl[1] = -1;
break;
}
if (fcntl(pulse_ctl[0], F_SETFL, O_NONBLOCK) == -1)
{
logger(Sound, Error, "pulse_context_init(), fcntl: %s", strerror(errno));
break;
}
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
context =
pa_context_new_with_proplist(pa_threaded_mainloop_get_api(mainloop), NULL,
proplist);
#else
context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "rdesktop");
2018-10-29 15:50:44 +01:00
#endif
if (context == NULL)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_context_init(), error creating PulseAudio context");
break;
}
pa_context_set_state_callback(context, pulse_context_state_cb, mainloop);
/* PulseAudio context connection */
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,15)
flags = PA_CONTEXT_NOFAIL;
2018-10-29 15:50:44 +01:00
#else
flags = 0;
2018-10-29 15:50:44 +01:00
#endif
if (pa_context_connect(context, NULL, flags, NULL) != 0)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_context_init(), %s", pa_strerror(err));
break;
}
do
{
context_state = pa_context_get_state(context);
if (context_state == PA_CONTEXT_READY || context_state == PA_CONTEXT_FAILED)
break;
else
pa_threaded_mainloop_wait(mainloop);
}
while (1);
if (context_state != PA_CONTEXT_READY)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_context_init(), %s", pa_strerror(err));
break;
}
ret = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
return ret;
}
static void
pulse_context_deinit(void)
{
int err;
if (context != NULL)
{
pa_threaded_mainloop_lock(mainloop);
pa_context_disconnect(context);
pa_context_unref(context);
context = NULL;
pa_threaded_mainloop_unlock(mainloop);
}
if (pulse_ctl[0] != -1)
{
do
err = close(pulse_ctl[0]);
while (err == -1 && errno == EINTR);
if (err == -1)
logger(Sound, Error, "pulse_context_deinit(), close: %s", strerror(errno));
pulse_ctl[0] = -1;
}
if (pulse_ctl[1] != -1)
{
do
err = close(pulse_ctl[1]);
while (err == -1 && errno == EINTR);
if (err == -1)
logger(Sound, Error, "pulse_context_deinit(), close: %s", strerror(errno));
pulse_ctl[1] = 0;
}
}
static RD_BOOL
2018-10-29 15:50:44 +01:00
pulse_stream_open(pa_stream ** stream, int channels, int samplerate, int samplewidth,
pa_stream_flags_t flags)
{
pa_sample_spec samples;
pa_buffer_attr buffer_attr;
pa_stream_state_t state;
int err;
RD_BOOL ret = False;
assert(stream != NULL);
assert(stream == &playback_stream || stream == &capture_stream);
logger(Sound, Debug, "pulse_stream_open(), channels=%d, samplerate=%d, samplewidth=%d",
channels, samplerate, samplewidth);
pa_threaded_mainloop_lock(mainloop);
do
{
/* PulseAudio sample format initialization */
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,13)
if (pa_sample_spec_init(&samples) == NULL)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_stream_open(), error initializing PulseAudio sample format");
break;
}
2018-10-29 15:50:44 +01:00
#endif
if (samplewidth == 2)
samples.format = PA_SAMPLE_S16LE;
else if (samplewidth == 1)
samples.format = PA_SAMPLE_U8;
else
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_stream_open(), wrong samplewidth for the PulseAudio stream: %d",
samplewidth);
break;
}
samples.rate = samplerate;
samples.channels = channels;
if (!pa_sample_spec_valid(&samples))
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_stream_open(), Invalid PulseAudio sample format");
break;
}
/* PulseAudio stream creation */
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
if (stream == &playback_stream)
2018-10-29 15:50:44 +01:00
*stream =
pa_stream_new_with_proplist(context, "Playback Stream", &samples,
NULL, proplist);
else
2018-10-29 15:50:44 +01:00
*stream =
pa_stream_new_with_proplist(context, "Capture Stream", &samples,
NULL, proplist);
#else
if (stream == &playback_stream)
*stream = pa_stream_new(context, "Playback Stream", &samples, NULL);
else
*stream = pa_stream_new(context, "Capture Stream", &samples, NULL);
2018-10-29 15:50:44 +01:00
#endif
if (*stream == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_stream_open(), pa_stream_new: %s",
pa_strerror(err));
break;
}
pa_stream_set_state_callback(*stream, pulse_stream_state_cb, mainloop);
2018-10-29 15:50:44 +01:00
buffer_attr.maxlength = (uint32_t) - 1;
buffer_attr.minreq = (uint32_t) - 1;
buffer_attr.prebuf = (uint32_t) - 1;
buffer_attr.tlength = (uint32_t) - 1;
buffer_attr.fragsize = (uint32_t) - 1;
/* PulseAudio stream connection */
if (stream == &playback_stream)
{
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,0)
buffer_attr.tlength =
pa_usec_to_bytes(1000000 / playback_latency_part, &samples);
#else
buffer_attr.tlength =
(samples.rate / playback_latency_part) * samples.channels *
(samples.format == PA_SAMPLE_S16LE ? 2 : 1);
#endif
buffer_attr.prebuf = 0;
buffer_attr.maxlength = buffer_attr.tlength;
}
else
{
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,0)
buffer_attr.fragsize =
pa_usec_to_bytes(1000000 / capture_latency_part, &samples);
#else
buffer_attr.fragsize =
(samples.rate / capture_latency_part) * samples.channels *
(samples.format == PA_SAMPLE_S16LE ? 2 : 1);
#endif
buffer_attr.maxlength = buffer_attr.fragsize;
}
2018-10-29 15:50:44 +01:00
#if !PA_CHECK_VERSION(0,9,16)
buffer_attr.minreq = (samples.rate / 50) * samples.channels * (samples.format == PA_SAMPLE_S16LE ? 2 : 1); // 20 ms
#endif
if (stream == &playback_stream)
2018-10-29 15:50:44 +01:00
err = pa_stream_connect_playback(*stream, device, &buffer_attr, flags, NULL,
NULL);
else
err = pa_stream_connect_record(*stream, device, &buffer_attr, flags);
if (err)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_stream_open(), error connecting PulseAudio stream: %s",
pa_strerror(err));
break;
}
do
{
state = pa_stream_get_state(*stream);
if (state == PA_STREAM_READY || state == PA_STREAM_FAILED)
break;
else
pa_threaded_mainloop_wait(mainloop);
}
while (1);
if (state != PA_STREAM_READY)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_stream_open(), error connecting PulseAudio stream: %s",
pa_strerror(err));
break;
}
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,8)
logger(Sound, Debug, "pulse_stream_open(), opened PulseAudio stream on device %s",
pa_stream_get_device_name(*stream));
2018-10-29 15:50:44 +01:00
#endif
#if PA_CHECK_VERSION(0,9,0)
const pa_buffer_attr *res_ba;
res_ba = pa_stream_get_buffer_attr(*stream);
2018-10-29 15:50:44 +01:00
logger(Sound, Debug,
"pulse_stream_open(), PulseAudio stream buffer metrics: maxlength %u, minreq %u, prebuf %u, tlength %u, fragsize %u",
res_ba->maxlength, res_ba->minreq, res_ba->prebuf, res_ba->tlength,
res_ba->fragsize);
#endif
/* Set the data callbacks for the PulseAudio stream */
if (stream == &playback_stream)
pa_stream_set_write_callback(*stream, pulse_write_cb, mainloop);
else
pa_stream_set_read_callback(*stream, pulse_read_cb, mainloop);
ret = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
return ret;
}
static void
pulse_stream_close(pa_stream ** stream)
{
pa_stream_state_t state;
int err;
assert(stream != NULL);
if (*stream != NULL)
{
pa_threaded_mainloop_lock(mainloop);
state = pa_stream_get_state(*stream);
if (state == PA_STREAM_READY)
{
if (pa_stream_disconnect(*stream) != 0)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_stream_close(), pa_stream_disconnect: %s\n",
pa_strerror(err));
}
}
pa_stream_unref(*stream);
*stream = NULL;
pa_threaded_mainloop_unlock(mainloop);
}
}
static void
pulse_send_msg(int fd, char message)
{
int ret;
do
ret = write(fd, &message, sizeof message);
while (ret == -1 && errno == EINTR);
if (ret == -1)
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_send_msg(), error writing message to the pipe: %s\n",
strerror(errno));
}
static RD_BOOL
pulse_playback_start(void)
{
RD_BOOL result = False;
int ret;
int err;
pa_operation *po;
if (playback_stream == NULL)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Warning,
"pulse_playback_start(), trying to start PulseAudio stream while it's not exists");
return True;
}
pa_threaded_mainloop_lock(mainloop);
do
{
if (pa_stream_get_state(playback_stream) != PA_STREAM_READY)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Warning,
"pulse_playback_start(), trying to start PulseAudio stream while it's not ready");
break;
}
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
ret = pa_stream_is_corked(playback_stream);
2018-10-29 15:50:44 +01:00
#else
ret = 1;
2018-10-29 15:50:44 +01:00
#endif
if (ret < 0)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_playback_start(), pa_stream_is_corked: %s",
pa_strerror(err));
break;
}
else if (ret != 0)
{
po = pa_stream_cork(playback_stream, 0, pulse_cork_cb, mainloop);
if (po == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_playback_start(), pa_stream_corked: %s",
pa_strerror(err));
break;
}
while (pa_operation_get_state(po) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(po);
}
result = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
return result;
}
static RD_BOOL
pulse_playback_stop(void)
{
RD_BOOL result = False;
int ret;
int err;
pa_operation *po;
if (playback_stream == NULL)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Debug,
"pulse_playback_stop(), trying to stop PulseAudio stream while it's not exists");
return True;
}
pa_threaded_mainloop_lock(mainloop);
do
{
if (pa_stream_get_state(playback_stream) != PA_STREAM_READY)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_playback_stop(), trying to stop PulseAudio stream while it's not ready");
break;
}
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
ret = pa_stream_is_corked(playback_stream);
2018-10-29 15:50:44 +01:00
#else
ret = 0;
2018-10-29 15:50:44 +01:00
#endif
if (ret < 0)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_playback_stop(), pa_stream_is_corked: %s",
pa_strerror(err));
break;
}
else if (ret == 0)
{
po = pa_stream_cork(playback_stream, 1, pulse_cork_cb, mainloop);
if (po == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_playback_stop(), pa_stream_cork: %s",
pa_strerror(err));
break;
}
while (pa_operation_get_state(po) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(po);
}
po = pa_stream_flush(playback_stream, pulse_flush_cb, mainloop);
if (po == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_playback_stop(), pa_stream_flush: %s",
pa_strerror(err));
break;
}
while (pa_operation_get_state(po) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(po);
result = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
return result;
}
static RD_BOOL
pulse_playback_set_audio(int channels, int samplerate, int samplewidth)
{
pa_stream_flags_t flags;
pulse_stream_close(&playback_stream);
2018-10-29 15:50:44 +01:00
flags = PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_AUTO_TIMING_UPDATE;
#if PA_CHECK_VERSION(0,9,11)
flags |= PA_STREAM_ADJUST_LATENCY;
2018-10-29 15:50:44 +01:00
#endif
if (pulse_stream_open(&playback_stream, channels, samplerate, samplewidth, flags) != True)
return False;
return True;
}
static RD_BOOL
pulse_capture_start(void)
{
RD_BOOL result = False;
int ret;
int err;
pa_operation *po;
if (capture_stream == NULL)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Warning,
"pulse_capture_start(), trying to start PulseAudio stream while it's not exists");
return True;
}
pa_threaded_mainloop_lock(mainloop);
do
{
if (pa_stream_get_state(capture_stream) != PA_STREAM_READY)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_capture_start(), trying to start PulseAudio stream while it's not exists");
break;
}
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
ret = pa_stream_is_corked(capture_stream);
2018-10-29 15:50:44 +01:00
#else
ret = 1;
2018-10-29 15:50:44 +01:00
#endif
if (ret < 0)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_capture_start(), pa_stream_is_corked: %s",
pa_strerror(err));
break;
}
else if (ret != 0)
{
po = pa_stream_cork(capture_stream, 0, pulse_cork_cb, mainloop);
if (po == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_capture_start(), pa_stream_cork: %s\n",
pa_strerror(err));
break;
}
while (pa_operation_get_state(po) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(po);
}
result = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
return result;
}
static RD_BOOL
pulse_capture_stop(void)
{
RD_BOOL result = False;
int ret;
int err;
pa_operation *po;
if (capture_stream == NULL)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Debug,
"pulse_capture_stop(), trying to stop PulseAudio stream while it's not exists");
return True;
}
pa_threaded_mainloop_lock(mainloop);
do
{
if (pa_stream_get_state(capture_stream) != PA_STREAM_READY)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_capture_stop(), trying to stop PulseAudio stream while it's not exists");
break;
}
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
ret = pa_stream_is_corked(capture_stream);
2018-10-29 15:50:44 +01:00
#else
ret = 0;
2018-10-29 15:50:44 +01:00
#endif
if (ret < 0)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_capture_stop(), pa_stream_is_corked: %s\n",
pa_strerror(err));
break;
}
else if (ret == 0)
{
po = pa_stream_cork(capture_stream, 1, pulse_cork_cb, mainloop);
if (po == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_capture_stop(), pa_stream_cork: %s\n",
pa_strerror(err));
break;
}
while (pa_operation_get_state(po) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(po);
}
result = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
return result;
}
static RD_BOOL
pulse_capture_set_audio(int channels, int samplerate, int samplewidth)
{
pa_stream_flags_t flags;
pa_stream_state_t state;
int ret;
int err;
flags = PA_STREAM_START_CORKED;
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
flags |= PA_STREAM_ADJUST_LATENCY;
2018-10-29 15:50:44 +01:00
#endif
if (capture_stream != NULL)
{
pa_threaded_mainloop_lock(mainloop);
state = pa_stream_get_state(capture_stream);
if (state == PA_STREAM_READY)
{
2018-10-29 15:50:44 +01:00
#if PA_CHECK_VERSION(0,9,11)
ret = pa_stream_is_corked(capture_stream);
2018-10-29 15:50:44 +01:00
#else
ret = (capture_started == False);
2018-10-29 15:50:44 +01:00
#endif
if (ret == 0)
flags &= ~PA_STREAM_START_CORKED;
else if (ret < 0)
{
err = pa_context_errno(context);
pa_threaded_mainloop_unlock(mainloop);
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_capture_set_audio(), pa_stream_is_corked: %s\n",
pa_strerror(err));
return False;
}
}
pa_threaded_mainloop_unlock(mainloop);
}
pulse_stream_close(&capture_stream);
if (pulse_stream_open(&capture_stream, channels, samplerate, samplewidth, flags) != True)
return False;
return True;
}
static void
2018-10-29 15:50:44 +01:00
pulse_context_state_cb(pa_context * c, void *userdata)
{
pa_context_state_t state;
assert(userdata != NULL);
state = pa_context_get_state(c);
if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED)
pa_threaded_mainloop_signal((pa_threaded_mainloop *) userdata, 0);
}
static void
2018-10-29 15:50:44 +01:00
pulse_stream_state_cb(pa_stream * p, void *userdata)
{
pa_stream_state_t state;
assert(userdata != NULL);
state = pa_stream_get_state(p);
if (state == PA_STREAM_FAILED)
{
if (p == playback_stream)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Debug,
"pulse_stream_state_cb(), PulseAudio playback stream is in a fail state");
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_OUT_ERR);
}
else
{
2018-10-29 15:50:44 +01:00
logger(Sound, Debug,
"pulse_stream_state_cb(), PulseAudio capture stream is in a fail state");
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_IN_ERR);
}
}
if (state == PA_STREAM_READY || state == PA_STREAM_FAILED)
pa_threaded_mainloop_signal((pa_threaded_mainloop *) userdata, 0);
}
static void
2018-10-29 15:50:44 +01:00
pulse_read_cb(pa_stream * p, size_t nbytes, void *userdata)
{
assert(userdata != NULL);
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_IN_AVAIL);
}
static void
2018-10-29 15:50:44 +01:00
pulse_write_cb(pa_stream * p, size_t nbytes, void *userdata)
{
assert(userdata != NULL);
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_OUT_AVAIL);
}
static void
2018-10-29 15:50:44 +01:00
pulse_cork_cb(pa_stream * p, int success, void *userdata)
{
assert(userdata != NULL);
if (!success)
{
if (p == playback_stream)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Warning,
"pulse_cork_cb(), fail to cork/uncork the PulseAudio playback stream: %s",
pa_strerror(pa_context_errno(context)));
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_OUT_ERR);
}
else
{
2018-10-29 15:50:44 +01:00
logger(Sound, Warning,
"pulse_cork_cb(), fail to cork/uncork the PulseAudio capture stream: %s",
pa_strerror(pa_context_errno(context)));
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_IN_ERR);
}
}
pa_threaded_mainloop_signal((pa_threaded_mainloop *) userdata, 0);
}
static void
2018-10-29 15:50:44 +01:00
pulse_flush_cb(pa_stream * p, int success, void *userdata)
{
assert(userdata != NULL);
if (!success)
{
logger(Sound, Warning, "pulse_flush_cb(), Fail to flush the PulseAudio stream: %s",
pa_strerror(pa_context_errno(context)));
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_OUT_ERR);
}
pa_threaded_mainloop_signal((pa_threaded_mainloop *) userdata, 0);
}
static void
2018-10-29 15:50:44 +01:00
pulse_update_timing_cb(pa_stream * p, int success, void *userdata)
{
assert(userdata != NULL);
if (!success)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Warning,
"pulse_update_timing_cb(), fail to update timing info of the PulseAudio stream: %s",
pa_strerror(pa_context_errno(context)));
pulse_send_msg(pulse_ctl[1], RDPSND_PULSE_OUT_ERR);
}
pa_threaded_mainloop_signal((pa_threaded_mainloop *) userdata, 0);
}
void
pulse_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv)
{
if (pulse_ctl[0] != -1)
{
if (pulse_ctl[0] > *n)
*n = pulse_ctl[0];
FD_SET(pulse_ctl[0], rfds);
}
}
void
pulse_check_fds(fd_set * rfds, fd_set * wfds)
{
char audio_cmd;
int n;
if (pulse_ctl[0] == -1)
return;
if (FD_ISSET(pulse_ctl[0], rfds))
{
do
{
n = read(pulse_ctl[0], &audio_cmd, sizeof audio_cmd);
if (n == -1)
{
if (errno == EINTR)
continue;
else if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
else
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_check_fds(), read: %s\n",
strerror(errno));
return;
}
}
else if (n == 0)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Warning,
"pulse_check_fds(), audio control pipe was closed");
break;
}
else
switch (audio_cmd)
{
case RDPSND_PULSE_OUT_AVAIL:
if (pulse_play() != True)
if (pulse_recover(&playback_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_check_fds(), PulseAudio playback error");
return;
}
break;
case RDPSND_PULSE_IN_AVAIL:
if (pulse_record() != True)
if (pulse_recover(&capture_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_check_fds(), PulseAudio capture error");
return;
}
break;
case RDPSND_PULSE_OUT_ERR:
if (pulse_recover(&playback_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_check_fds(), an error occured in audio thread with PulseAudio playback stream");
return;
}
break;
case RDPSND_PULSE_IN_ERR:
if (pulse_recover(&capture_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_check_fds(), an error occured in audio thread with PulseAudio capture stream");
return;
}
break;
default:
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_check_fds(), wrong command from the audio thread: %d",
audio_cmd);
break;
}
}
while (1);
}
return;
}
RD_BOOL
pulse_open_out(void)
{
if (context == NULL || mainloop == NULL)
if (pulse_init() != True)
return False;
return True;
}
void
pulse_close_out(void)
{
/* Ack all remaining packets */
while (!rdpsnd_queue_empty())
rdpsnd_queue_next(0);
playback_started = False;
if (playback_stream && pulse_playback_stop() != True)
if (pulse_recover(&playback_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_close_out(), fail to close the PulseAudio playback stream");
return;
}
}
RD_BOOL
pulse_format_supported(RD_WAVEFORMATEX * pwfx)
{
if (pwfx->wFormatTag != WAVE_FORMAT_PCM)
return False;
if ((pwfx->nChannels != 1) && (pwfx->nChannels != 2))
return False;
if ((pwfx->wBitsPerSample != 8) && (pwfx->wBitsPerSample != 16))
return False;
return True;
}
RD_BOOL
pulse_set_format_out(RD_WAVEFORMATEX * pwfx)
{
if (playback_stream == NULL
2018-10-29 15:50:44 +01:00
|| playback_channels != pwfx->nChannels
|| playback_samplerate != pwfx->nSamplesPerSec
|| playback_samplewidth != pwfx->wBitsPerSample / 8)
{
playback_channels = pwfx->nChannels;
playback_samplerate = pwfx->nSamplesPerSec;
playback_samplewidth = pwfx->wBitsPerSample / 8;
2018-10-29 15:50:44 +01:00
if (pulse_playback_set_audio
(pwfx->nChannels, pwfx->nSamplesPerSec, pwfx->wBitsPerSample / 8) != True)
if (pulse_recover(&playback_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_set_format_out(), fail to open the PulseAudio playback stream");
return False;
}
}
playback_started = True;
if (pulse_playback_start() != True)
if (pulse_recover(&playback_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_set_format_out(), fail to start the PulseAudio playback stream");
return False;
}
return True;
}
RD_BOOL
pulse_play(void)
{
struct audio_packet *packet;
STREAM out;
const pa_timing_info *ti;
pa_operation *po;
pa_seek_mode_t playback_seek;
size_t avail_space, audio_size;
pa_usec_t delay = 0;
int ret;
int err;
RD_BOOL result = False;
if (rdpsnd_queue_empty())
return True;
if (playback_stream == NULL)
return False;
pa_threaded_mainloop_lock(mainloop);
do
{
packet = rdpsnd_queue_current_packet();
out = packet->s;
ti = pa_stream_get_timing_info(playback_stream);
if (ti == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_play(), pa_stream_get_timing_info: %s",
pa_strerror(err));
break;
}
if (ti->read_index_corrupt || ti->write_index_corrupt)
{
2018-10-29 15:50:44 +01:00
po = pa_stream_update_timing_info(playback_stream, pulse_update_timing_cb,
mainloop);
if (po == NULL)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_play(), pa_stream_update_timing_info: %s",
pa_strerror(err));
break;
}
while (pa_operation_get_state(po) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(po);
}
if (ti->read_index > ti->write_index)
{
logger(Sound, Debug, "pulse_play(), PulseAudio stream underflow %ld bytes",
(long) (ti->read_index - ti->write_index));
playback_seek = PA_SEEK_RELATIVE_ON_READ;
}
else
playback_seek = PA_SEEK_RELATIVE;
avail_space = pa_stream_writable_size(playback_stream);
audio_size = MIN(s_remaining(out), avail_space);
if (audio_size)
{
unsigned char *data;
in_uint8p(out, data, audio_size);
2018-10-29 15:50:44 +01:00
if (pa_stream_write
(playback_stream, data, audio_size, NULL, 0, playback_seek) != 0)
{
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_play(), pa_stream_write: %s",
pa_strerror(err));
break;
}
else if (playback_seek == PA_SEEK_RELATIVE_ON_READ)
playback_seek = PA_SEEK_RELATIVE;
}
if (s_check_end(out))
{
ret = pa_stream_get_latency(playback_stream, &delay, NULL);
if (ret != 0 && (err = pa_context_errno(context)) == PA_ERR_NODATA)
{
2018-10-29 15:50:44 +01:00
po = pa_stream_update_timing_info(playback_stream,
pulse_update_timing_cb, mainloop);
if (po == NULL)
{
delay = 0;
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_play(), pa_stream_update_timing_info: %s",
pa_strerror(err));
break;
}
while (pa_operation_get_state(po) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(po);
ret = pa_stream_get_latency(playback_stream, &delay, NULL);
}
if (ret != 0)
{
delay = 0;
err = pa_context_errno(context);
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_play(), pa_stream_get_latency: %s",
pa_strerror(err));
break;
}
2018-10-29 15:50:44 +01:00
logger(Sound, Debug,
"pulse_play(), PulseAudio playback stream latency %lu usec",
(long) delay);
}
result = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
if (s_check_end(out))
rdpsnd_queue_next(delay);
return result;
}
RD_BOOL
pulse_open_in(void)
{
if (context == NULL || mainloop == NULL)
if (pulse_init() != True)
return False;
return True;
}
void
pulse_close_in(void)
{
capture_started = False;
if (capture_stream && pulse_capture_stop() != True)
if (pulse_recover(&capture_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_close_in(), fail to close the PulseAudio capture stream");
return;
}
}
RD_BOOL
pulse_set_format_in(RD_WAVEFORMATEX * pwfx)
{
if (capture_stream == NULL
2018-10-29 15:50:44 +01:00
|| capture_channels != pwfx->nChannels
|| capture_samplerate != pwfx->nSamplesPerSec
|| capture_samplewidth != pwfx->wBitsPerSample / 8)
{
capture_channels = pwfx->nChannels;
capture_samplerate = pwfx->nSamplesPerSec;
capture_samplewidth = pwfx->wBitsPerSample / 8;
2018-10-29 15:50:44 +01:00
if (pulse_capture_set_audio
(pwfx->nChannels, pwfx->nSamplesPerSec, pwfx->wBitsPerSample / 8) != True)
if (pulse_recover(&capture_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_set_format_in(), fail to open the PulseAudio capture stream");
return False;
}
}
capture_started = True;
if (pulse_capture_start() != True)
if (pulse_recover(&capture_stream) != True)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error,
"pulse_set_format_in(), fail to start the PulseAudio capture stream");
return False;
}
return True;
}
RD_BOOL
pulse_record(void)
{
const void *pulse_buf;
size_t audio_size;
RD_BOOL result = False;
if (capture_stream == NULL)
return False;
pa_threaded_mainloop_lock(mainloop);
do
{
if (pa_stream_peek(capture_stream, &pulse_buf, &audio_size) != 0)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_record(), pa_stream_peek: %s",
pa_strerror(pa_context_errno(context)));
break;
}
/* Stretch the buffer, if needed */
if (capture_buf_size < audio_size)
{
capture_buf_size = audio_size;
if (capture_buf != NULL)
free(capture_buf);
capture_buf = malloc(capture_buf_size);
if (capture_buf == NULL)
{
logger(Sound, Error, "pulse_record(), malloc error");
capture_buf_size = 0;
break;
}
}
memcpy(capture_buf, pulse_buf, audio_size);
if (pa_stream_drop(capture_stream) != 0)
{
2018-10-29 15:50:44 +01:00
logger(Sound, Error, "pulse_record(), pa_stream_drop: %s",
pa_strerror(pa_context_errno(context)));
break;
}
result = True;
}
while (0);
pa_threaded_mainloop_unlock(mainloop);
if (result == True)
rdpsnd_record(capture_buf, audio_size);
return result;
}
static RD_BOOL
pulse_recover(pa_stream ** stream)
{
RD_BOOL playback, capture;
playback = capture = False;
if (playback_stream != NULL)
playback = True;
if (capture_stream != NULL)
capture = True;
if (stream == &playback_stream)
{
2018-10-29 15:50:44 +01:00
if (pulse_playback_set_audio
(playback_channels, playback_samplerate, playback_samplewidth) == True)
if (playback_started != True || pulse_playback_start() == True)
return True;
}
else if (stream == &capture_stream)
{
2018-10-29 15:50:44 +01:00
if (pulse_capture_set_audio
(capture_channels, capture_samplerate, capture_samplewidth) == True)
if (capture_started != True || pulse_capture_start() == True)
return True;
}
pulse_deinit();
if (pulse_init() != True)
return False;
do
{
if (playback == True)
{
2018-10-29 15:50:44 +01:00
if (pulse_playback_set_audio
(playback_channels, playback_samplerate, playback_samplewidth) != True
|| (playback_started == True && pulse_playback_start() != True))
break;
}
if (capture == True)
{
2018-10-29 15:50:44 +01:00
if (pulse_capture_set_audio
(capture_channels, capture_samplerate, capture_samplewidth) != True
|| (capture_started == True && pulse_capture_start() != True))
break;
}
return True;
}
while (0);
pulse_deinit();
return False;
}
struct audio_driver *
pulse_register(char *options)
{
static struct audio_driver pulse_driver;
memset(&pulse_driver, 0, sizeof(pulse_driver));
pulse_driver.name = "pulse";
pulse_driver.description = "PulseAudio output driver, default device: system dependent";
pulse_driver.add_fds = pulse_add_fds;
pulse_driver.check_fds = pulse_check_fds;
pulse_driver.wave_out_open = pulse_open_out;
pulse_driver.wave_out_close = pulse_close_out;
pulse_driver.wave_out_format_supported = pulse_format_supported;
pulse_driver.wave_out_set_format = pulse_set_format_out;
pulse_driver.wave_out_volume = rdpsnd_dsp_softvol_set;
pulse_driver.wave_in_open = pulse_open_in;
pulse_driver.wave_in_close = pulse_close_in;
pulse_driver.wave_in_format_supported = pulse_format_supported;
pulse_driver.wave_in_set_format = pulse_set_format_in;
pulse_driver.wave_in_volume = NULL; /* FIXME */
pulse_driver.need_byteswap_on_be = 0;
pulse_driver.need_resampling = 0;
if (options != NULL)
{
device = xstrdup(options);
}
else
{
device = DEFAULTDEVICE;
}
return &pulse_driver;
}