2017-09-26 15:07:14 +02:00
|
|
|
/* -*- 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
|
2017-09-26 15:07:14 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
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
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
/* 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);
|
2017-09-26 15:07:14 +02:00
|
|
|
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);
|
2017-09-26 15:07:14 +02:00
|
|
|
|
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);
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#if PA_CHECK_VERSION(0,9,11)
|
2017-09-26 15:07:14 +02:00
|
|
|
/* 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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
if (proplist != NULL)
|
|
|
|
{
|
|
|
|
pa_proplist_free(proplist);
|
|
|
|
proplist = NULL;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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
|
2017-09-26 15:07:14 +02:00
|
|
|
context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "rdesktop");
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
if (context == NULL)
|
|
|
|
{
|
2018-10-29 15:50:44 +01:00
|
|
|
logger(Sound, Error,
|
|
|
|
"pulse_context_init(), error creating PulseAudio context");
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
flags = PA_CONTEXT_NOFAIL;
|
2018-10-29 15:50:44 +01:00
|
|
|
#else
|
2017-09-26 15:07:14 +02:00
|
|
|
flags = 0;
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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);
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* PulseAudio stream creation */
|
2018-10-29 15:50:44 +01:00
|
|
|
#if PA_CHECK_VERSION(0,9,11)
|
2017-09-26 15:07:14 +02:00
|
|
|
if (stream == &playback_stream)
|
2018-10-29 15:50:44 +01:00
|
|
|
*stream =
|
|
|
|
pa_stream_new_with_proplist(context, "Playback Stream", &samples,
|
|
|
|
NULL, proplist);
|
2017-09-26 15:07:14 +02:00
|
|
|
else
|
2018-10-29 15:50:44 +01:00
|
|
|
*stream =
|
|
|
|
pa_stream_new_with_proplist(context, "Capture Stream", &samples,
|
|
|
|
NULL, proplist);
|
|
|
|
#else
|
2017-09-26 15:07:14 +02:00
|
|
|
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
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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;
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
/* 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
|
2017-09-26 15:07:14 +02:00
|
|
|
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
|
2017-09-26 15:07:14 +02:00
|
|
|
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
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
if (stream == &playback_stream)
|
2018-10-29 15:50:44 +01:00
|
|
|
err = pa_stream_connect_playback(*stream, device, &buffer_attr, flags, NULL,
|
|
|
|
NULL);
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-10-29 15:50:44 +01:00
|
|
|
#if PA_CHECK_VERSION(0,9,8)
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
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
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
/* 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));
|
2017-09-26 15:07:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#if PA_CHECK_VERSION(0,9,11)
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = pa_stream_is_corked(playback_stream);
|
2018-10-29 15:50:44 +01:00
|
|
|
#else
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = 1;
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#if PA_CHECK_VERSION(0,9,11)
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = pa_stream_is_corked(playback_stream);
|
2018-10-29 15:50:44 +01:00
|
|
|
#else
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = 0;
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
flags |= PA_STREAM_ADJUST_LATENCY;
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#if PA_CHECK_VERSION(0,9,11)
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = pa_stream_is_corked(capture_stream);
|
2018-10-29 15:50:44 +01:00
|
|
|
#else
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = 1;
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-10-29 15:50:44 +01:00
|
|
|
#if PA_CHECK_VERSION(0,9,11)
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = pa_stream_is_corked(capture_stream);
|
2018-10-29 15:50:44 +01:00
|
|
|
#else
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = 0;
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
flags |= PA_STREAM_ADJUST_LATENCY;
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = pa_stream_is_corked(capture_stream);
|
2018-10-29 15:50:44 +01:00
|
|
|
#else
|
2017-09-26 15:07:14 +02:00
|
|
|
ret = (capture_started == False);
|
2018-10-29 15:50:44 +01:00
|
|
|
#endif
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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",
|
2017-09-26 15:07:14 +02:00
|
|
|
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",
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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",
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (n == 0)
|
|
|
|
{
|
2018-10-29 15:50:44 +01:00
|
|
|
logger(Sound, Warning,
|
|
|
|
"pulse_check_fds(), audio control pipe was closed");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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);
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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();
|
2019-04-09 13:25:50 +02:00
|
|
|
out = packet->s;
|
2017-09-26 15:07:14 +02:00
|
|
|
|
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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);
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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);
|
2019-04-09 13:13:35 +02:00
|
|
|
audio_size = MIN(s_remaining(out), avail_space);
|
2017-09-26 15:07:14 +02:00
|
|
|
if (audio_size)
|
|
|
|
{
|
2018-10-29 15:50:44 +01:00
|
|
|
if (pa_stream_write
|
|
|
|
(playback_stream, out->p, audio_size, NULL, 0, playback_seek) != 0)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (playback_seek == PA_SEEK_RELATIVE_ON_READ)
|
|
|
|
playback_seek = PA_SEEK_RELATIVE;
|
|
|
|
}
|
|
|
|
|
|
|
|
out->p += audio_size;
|
|
|
|
|
|
|
|
if (out->p == out->end)
|
|
|
|
{
|
|
|
|
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);
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
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));
|
2017-09-26 15:07:14 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-10-29 15:50:44 +01:00
|
|
|
logger(Sound, Debug,
|
|
|
|
"pulse_play(), PulseAudio playback stream latency %lu usec",
|
|
|
|
(long) delay);
|
2017-09-26 15:07:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result = True;
|
|
|
|
}
|
|
|
|
while (0);
|
|
|
|
|
|
|
|
pa_threaded_mainloop_unlock(mainloop);
|
|
|
|
|
|
|
|
if (out->p == out->end)
|
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
{
|
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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");
|
2017-09-26 15:07:14 +02:00
|
|
|
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)));
|
2017-09-26 15:07:14 +02:00
|
|
|
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)));
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
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)
|
2017-09-26 15:07:14 +02:00
|
|
|
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;
|
2017-09-26 15:07:14 +02:00
|
|
|
}
|
|
|
|
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))
|
2017-09-26 15:07:14 +02:00
|
|
|
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;
|
|
|
|
}
|