[Spice-devel] [xf86-video-qxl PATCH (resend 2)] Implement sending audio to the client from a directory of FIFO queues
Jeremy White
jwhite at codeweavers.com
Fri Mar 15 07:22:29 PDT 2013
On 03/15/2013 09:19 AM, Andrew Eikum wrote:
> This introduces a new Xorg.conf option, SpicePlaybackFIFODir, which will
> be monitored for files. The XSpice driver will mix and forward the audio
> data sent to those pipes to the Spice client.
>
> This is designed to work with PulseAudio's module-pipe-sink, but should
> work with any audio output to a pipe. For example, use with this PA
> configuration option:
>
> load-module module-pipe-sink file=$FIFO_DIR/playback.fifo format=s16 rate=44100 channels=2
>
> making sure the format, rate, and channels match the Spice protocol
> settings.
> ---
>
> Resending again, with fixed code format.
Ack, to the extent that if this option is *not* set, I believe it will
do no harm (it will generate a log message saying it's disabled in
Xspice mode).
Cheers,
Jeremy
>
> src/Makefile.am | 2 +
> src/qxl.h | 6 +
> src/qxl_driver.c | 18 ++-
> src/spiceqxl_audio.c | 343 +++++++++++++++++++++++++++++++++++++++++++++++++++
> src/spiceqxl_audio.h | 31 +++++
> 5 files changed, 399 insertions(+), 1 deletion(-)
> create mode 100644 src/spiceqxl_audio.c
> create mode 100644 src/spiceqxl_audio.h
>
> diff --git a/src/Makefile.am b/src/Makefile.am
> index f9557da..8632297 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -82,6 +82,8 @@ spiceqxl_drv_la_SOURCES = \
> spiceqxl_main_loop.h \
> spiceqxl_display.c \
> spiceqxl_display.h \
> + spiceqxl_audio.c \
> + spiceqxl_audio.h \
> spiceqxl_inputs.c \
> spiceqxl_inputs.h \
> qxl_driver.c \
> diff --git a/src/qxl.h b/src/qxl.h
> index 017eab5..c26ea8f 100644
> --- a/src/qxl.h
> +++ b/src/qxl.h
> @@ -130,6 +130,7 @@ enum {
> OPTION_SPICE_DH_FILE,
> OPTION_SPICE_DEFERRED_FPS,
> OPTION_SPICE_EXIT_ON_DISCONNECT,
> + OPTION_SPICE_PLAYBACK_FIFO_DIR,
> #endif
> OPTION_COUNT,
> };
> @@ -284,9 +285,12 @@ struct _qxl_screen_t
> QXLWorker * worker;
> int worker_running;
> QXLInstance display_sin;
> + SpicePlaybackInstance playback_sin;
> /* XSpice specific, dragged from the Device */
> QXLReleaseInfo *last_release;
>
> + pthread_t audio_thread;
> +
> uint32_t cmdflags;
> uint32_t oom_running;
> uint32_t num_free_res; /* is having a release ring effective
> @@ -304,6 +308,8 @@ struct _qxl_screen_t
> } guest_primary;
>
> uint32_t deferred_fps;
> +
> + char playback_fifo_dir[PATH_MAX];
> #endif /* XSPICE */
>
> struct xorg_list ums_bos;
> diff --git a/src/qxl_driver.c b/src/qxl_driver.c
> index 1d58f01..335e095 100644
> --- a/src/qxl_driver.c
> +++ b/src/qxl_driver.c
> @@ -55,6 +55,7 @@
> #include "spiceqxl_io_port.h"
> #include "spiceqxl_spice_server.h"
> #include "dfps.h"
> +#include "spiceqxl_audio.h"
> #endif /* XSPICE */
>
> extern void compat_init_scrn (ScrnInfoPtr);
> @@ -121,6 +122,8 @@ const OptionInfoRec DefaultOptions[] =
> "SpiceDeferredFPS", OPTV_INTEGER, {0}, FALSE},
> { OPTION_SPICE_EXIT_ON_DISCONNECT,
> "SpiceExitOnDisconnect", OPTV_BOOLEAN, {0}, FALSE},
> + { OPTION_SPICE_PLAYBACK_FIFO_DIR,
> + "SpicePlaybackFIFODir", OPTV_STRING, {0}, FALSE},
> #endif
>
> { -1, NULL, OPTV_NONE, {0}, FALSE }
> @@ -639,6 +642,7 @@ spiceqxl_screen_init (ScrnInfoPtr pScrn, qxl_screen_t *qxl)
> qxl->core = basic_event_loop_init ();
> spice_server_init (qxl->spice_server, qxl->core);
> qxl_add_spice_display_interface (qxl);
> + qxl_add_spice_playback_interface (qxl);
> qxl->worker->start (qxl->worker);
> qxl->worker_running = TRUE;
> if (qxl->deferred_fps)
> @@ -1007,7 +1011,10 @@ qxl_pre_init (ScrnInfoPtr pScrn, int flags)
> //int *linePitches = NULL;
> //DisplayModePtr mode;
> unsigned int max_x, max_y;
> -
> +#ifdef XSPICE
> + const char *playback_fifo_dir;
> +#endif
> +
> /* In X server 1.7.5, Xorg -configure will cause this
> * function to get called without a confScreen.
> */
> @@ -1053,6 +1060,15 @@ qxl_pre_init (ScrnInfoPtr pScrn, int flags)
> if (!qxl_pre_init_common(pScrn))
> goto out;
>
> +#ifdef XSPICE
> + playback_fifo_dir = get_str_option(qxl->options, OPTION_SPICE_PLAYBACK_FIFO_DIR,
> + "XSPICE_PLAYBACK_FIFO_DIR");
> + if (playback_fifo_dir)
> + strncpy(qxl->playback_fifo_dir, playback_fifo_dir, sizeof(qxl->playback_fifo_dir));
> + else
> + qxl->playback_fifo_dir[0] = '\0';
> +#endif
> +
> if (!qxl_map_memory (qxl, scrnIndex))
> goto out;
>
> diff --git a/src/spiceqxl_audio.c b/src/spiceqxl_audio.c
> new file mode 100644
> index 0000000..3cd80ff
> --- /dev/null
> +++ b/src/spiceqxl_audio.c
> @@ -0,0 +1,343 @@
> +/*
> + * Copyright 2012 Andrew Eikum for CodeWeavers Inc.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * on the rights to use, copy, modify, merge, publish, distribute, sub
> + * license, and/or sell copies of the Software, and to permit persons to whom
> + * the Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
> + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
> + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include "config.h"
> +#endif
> +
> +#include "spiceqxl_audio.h"
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <pthread.h>
> +#include <sys/time.h>
> +#include <unistd.h>
> +#include <dirent.h>
> +
> +#define BUFFER_PERIODS 10
> +#define PERIOD_MS 10
> +#define MAX_FIFOS 16
> +
> +struct audio_data {
> + int fifo_fds[MAX_FIFOS];
> + ino_t inodes[MAX_FIFOS];
> + uint32_t valid_bytes, write_offs;
> + char *buffer, *spice_buffer;
> + int period_frames;
> + uint32_t spice_write_offs, spice_buffer_bytes;
> + uint32_t frame_bytes, period_bytes, fed, buffer_bytes;
> + struct timeval last_read_time;
> +};
> +
> +static ssize_t
> +read_from_fifos (struct audio_data *data)
> +{
> + size_t to_read_bytes = min(data->period_bytes, data->buffer_bytes - data->write_offs);
> + int i;
> + ssize_t max_readed = 0;
> + int16_t *out_buf = (int16_t*)(data->buffer + data->write_offs), *buf;
> +
> + buf = malloc(to_read_bytes);
> + if (!buf)
> + {
> + ErrorF("playback: malloc failed: %s\n", strerror(errno));
> + return 0;
> + }
> +
> + memset(out_buf, 0, to_read_bytes);
> +
> + for (i = 0; i < MAX_FIFOS; ++i)
> + {
> + unsigned int s;
> + ssize_t readed;
> +
> + if (data->fifo_fds[i] < 0)
> + continue;
> +
> + readed = read(data->fifo_fds[i], buf, to_read_bytes);
> + if (readed < 0)
> + {
> + if (errno != EAGAIN && errno != EINTR)
> + ErrorF("playback: read from FIFO %d failed: %s\n", data->fifo_fds[i], strerror(errno));
> + continue;
> + }
> +
> + if (readed == 0)
> + {
> + ErrorF("playback: FIFO %d gave EOF\n", data->fifo_fds[i]);
> + close(data->fifo_fds[i]);
> + data->fifo_fds[i] = -1;
> + data->inodes[i] = 0;
> + continue;
> + }
> +
> + if (readed > max_readed)
> + max_readed = readed;
> +
> + for (s = 0; s < readed / sizeof(int16_t); ++s)
> + {
> + /* FIXME: Ehhh, this'd be better as floats. With this algorithm,
> + * samples mixed after being clipped will have undue weight. But
> + * if we're clipping, then we're distorted anyway, so whatever. */
> + if (out_buf[s] + buf[s] > INT16_MAX)
> + out_buf[s] = INT16_MAX;
> + else if (out_buf[s] + buf[s] < -INT16_MAX)
> + out_buf[s] = -INT16_MAX;
> + else
> + out_buf[s] += buf[s];
> + }
> + }
> +
> + free(buf);
> +
> + if (!max_readed)
> + return 0;
> +
> + data->valid_bytes = min(data->valid_bytes + max_readed,
> + data->buffer_bytes);
> +
> + data->write_offs += max_readed;
> + data->write_offs %= data->buffer_bytes;
> +
> + ++data->fed;
> +
> + return max_readed;
> +}
> +
> +static int
> +scan_fifos (struct audio_data *data, const char *dirname)
> +{
> + DIR *dir;
> + struct dirent *ent;
> + int i;
> +
> + dir = opendir(dirname);
> + if (!dir)
> + {
> + ErrorF("playback: failed to open FIFO directory '%s': %s\n", dirname, strerror(errno));
> + return 1;
> + }
> +
> + while ((ent = readdir(dir)))
> + {
> + char path[PATH_MAX];
> +
> + if (ent->d_name[0] == '.')
> + /* skip dot-files */
> + continue;
> +
> + for (i = 0; i < MAX_FIFOS; ++i)
> + if (ent->d_ino == data->inodes[i])
> + break;
> + if (i < MAX_FIFOS)
> + /* file already open */
> + continue;
> +
> + for (i = 0; i < MAX_FIFOS; ++i)
> + if (data->fifo_fds[i] < 0)
> + break;
> + if (i == MAX_FIFOS)
> + {
> + static int once = 0;
> + if (!once)
> + {
> + ErrorF("playback: Too many FIFOs already open\n");
> + ++once;
> + }
> + closedir(dir);
> + return 0;
> + }
> +
> + strncpy(path, dirname, sizeof(path));
> + strncat(path, "/", sizeof(path));
> + strncat(path, ent->d_name, sizeof(path));
> +
> + data->fifo_fds[i] = open(path, O_RDONLY | O_RSYNC | O_NONBLOCK);
> + if (data->fifo_fds[i] < 0)
> + {
> + ErrorF("playback: open FIFO '%s' failed: %s\n", path, strerror(errno));
> + continue;
> + }
> + ErrorF("playback: opened FIFO '%s' as %d\n", path, data->fifo_fds[i]);
> +
> + data->inodes[i] = ent->d_ino;
> + }
> +
> + closedir(dir);
> +
> + return 0;
> +}
> +
> +static void *
> +audio_thread_main (void *p)
> +{
> + qxl_screen_t *qxl = p;
> + int i;
> + struct audio_data data;
> +
> + for (i = 0; i < MAX_FIFOS; ++i)
> + data.fifo_fds[i] = -1;
> +
> + data.valid_bytes = data.fed = 0;
> + data.period_frames = SPICE_INTERFACE_PLAYBACK_FREQ * PERIOD_MS / 1000;
> +
> + data.frame_bytes = sizeof(int16_t) * SPICE_INTERFACE_PLAYBACK_CHAN;
> +
> + data.period_bytes = data.period_frames * data.frame_bytes;
> + data.buffer_bytes = data.period_bytes * BUFFER_PERIODS;
> + data.buffer = malloc(data.buffer_bytes);
> + memset(data.buffer, 0, data.buffer_bytes);
> +
> + spice_server_playback_start(&qxl->playback_sin);
> +
> + gettimeofday(&data.last_read_time, NULL);
> +
> + while (1)
> + {
> + struct timeval end, diff, period_tv;
> +
> + if (scan_fifos(&data, qxl->playback_fifo_dir))
> + goto cleanup;
> +
> + while (data.fed < BUFFER_PERIODS)
> + {
> + if (!read_from_fifos(&data))
> + break;
> +
> + while (data.valid_bytes)
> + {
> + int to_copy_bytes;
> + uint32_t read_offs;
> +
> + if (!data.spice_buffer)
> + {
> + uint32_t chunk_frames;
> + spice_server_playback_get_buffer(&qxl->playback_sin, (uint32_t**)&data.spice_buffer, &chunk_frames);
> + data.spice_buffer_bytes = chunk_frames * data.frame_bytes;
> + }
> + if (!data.spice_buffer)
> + break;
> +
> + if (data.valid_bytes > data.write_offs)
> + {
> + read_offs = data.buffer_bytes + data.write_offs - data.valid_bytes;
> + to_copy_bytes = min(data.buffer_bytes - read_offs,
> + data.spice_buffer_bytes - data.spice_write_offs);
> + }
> + else
> + {
> + read_offs = data.write_offs - data.valid_bytes;
> + to_copy_bytes = min(data.valid_bytes,
> + data.spice_buffer_bytes - data.spice_write_offs);
> + }
> +
> + memcpy(data.spice_buffer + data.spice_write_offs,
> + data.buffer + read_offs, to_copy_bytes);
> +
> + data.valid_bytes -= to_copy_bytes;
> +
> + data.spice_write_offs += to_copy_bytes;
> +
> + if (data.spice_write_offs >= data.spice_buffer_bytes)
> + {
> + spice_server_playback_put_samples(&qxl->playback_sin, (uint32_t*)data.spice_buffer);
> + data.spice_buffer = NULL;
> + data.spice_buffer_bytes = data.spice_write_offs = 0;
> + }
> + }
> + }
> +
> + period_tv.tv_sec = 0;
> + period_tv.tv_usec = PERIOD_MS * 1000;
> +
> + usleep(period_tv.tv_usec);
> +
> + gettimeofday(&end, NULL);
> +
> + timersub(&end, &data.last_read_time, &diff);
> +
> + while (data.fed &&
> + (diff.tv_sec > 0 || diff.tv_usec >= period_tv.tv_usec))
> + {
> + timersub(&diff, &period_tv, &diff);
> +
> + --data.fed;
> +
> + timeradd(&data.last_read_time, &period_tv, &data.last_read_time);
> + }
> +
> + if (!data.fed)
> + data.last_read_time = end;
> + }
> +
> +cleanup:
> + if (data.spice_buffer)
> + {
> + memset(data.spice_buffer, 0, data.spice_buffer_bytes - data.spice_write_offs);
> + spice_server_playback_put_samples(&qxl->playback_sin, (uint32_t*)data.spice_buffer);
> + data.spice_buffer = NULL;
> + data.spice_buffer_bytes = data.spice_write_offs = 0;
> + }
> +
> + free(data.buffer);
> +
> + spice_server_playback_stop(&qxl->playback_sin);
> +
> + return NULL;
> +}
> +
> +static const SpicePlaybackInterface playback_sif = {
> + {
> + SPICE_INTERFACE_PLAYBACK,
> + "playback",
> + SPICE_INTERFACE_PLAYBACK_MAJOR,
> + SPICE_INTERFACE_PLAYBACK_MINOR
> + }
> +};
> +
> +int
> +qxl_add_spice_playback_interface (qxl_screen_t *qxl)
> +{
> + int ret;
> +
> + if (qxl->playback_fifo_dir[0] == 0)
> + {
> + ErrorF("playback: no audio FIFO directory, audio is disabled\n");
> + return 0;
> + }
> +
> + qxl->playback_sin.base.sif = &playback_sif.base;
> + ret = spice_server_add_interface(qxl->spice_server, &qxl->playback_sin.base);
> + if (ret < 0)
> + return errno;
> +
> + /* disable CELT */
> + ret = spice_server_set_playback_compression(qxl->spice_server, 0);
> + if (ret < 0)
> + return errno;
> +
> + ret = pthread_create(&qxl->audio_thread, NULL, &audio_thread_main, qxl);
> + if (ret < 0)
> + return errno;
> +
> + return 0;
> +}
> diff --git a/src/spiceqxl_audio.h b/src/spiceqxl_audio.h
> new file mode 100644
> index 0000000..695ba25
> --- /dev/null
> +++ b/src/spiceqxl_audio.h
> @@ -0,0 +1,31 @@
> +/*
> + * Copyright 2012 Andrew Eikum for CodeWeavers Inc.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * on the rights to use, copy, modify, merge, publish, distribute, sub
> + * license, and/or sell copies of the Software, and to permit persons to whom
> + * the Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
> + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
> + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
> + */
> +
> +#ifndef QXL_SPICE_AUDIO_H
> +#define QXL_SPICE_AUDIO_H
> +
> +#include "qxl.h"
> +#include <spice.h>
> +
> +int qxl_add_spice_playback_interface(qxl_screen_t *qxl);
> +
> +#endif
More information about the Spice-devel
mailing list