[Spice-devel] [PATCH] Implement sending audio to the client from a directory of FIFO queues
Andrew Eikum
aeikum at codeweavers.com
Tue Feb 12 10:40:34 PST 2013
On Tue, Feb 12, 2013 at 12:48:16PM -0500, Alon Levy wrote:
> > 1) The code starts up a new thread to do the mixing. Could we run
> > into
> > concurrency issues with mode switching, or during teardown? Doesn't
> > seem to have been a problem in practice.
>
> I don't know.
>
> >
> > 2) There doesn't seem to be much teardown code in the QXL driver, so
> > the audio thread effectively never quits until the driver exits.
> >
> > 3) I disabled the CELT compression because it corrupted the audio. I
> > guess CELT support may not be long for this world, so I didn't look
> > further into it. That means data is sent over the pipe raw.
>
> It's fine, raw audio > no audio, can always fix this later. And we
> all want Opus, but I don't see anyone working on it unfortunately.
>
> About the patch itself, it will be much easier to review (and I do
> have some comments - the main one being coding standard, but also
> about the mixing) if you inline the patch - could you please? thanks,
I was using the inline Content-Disposition, but Wikipedia informs me
most mail clients don't respect this field. I tried another method of
sending it, so hopefully this works better.
I believe I fixed the code format to conform with the rest of the
project. Sorry, I should have examined this before I sent the patch.
---
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 6950ba5..21d8dfc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -77,6 +77,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 8e2802a..29caf43 100644
--- a/src/qxl.h
+++ b/src/qxl.h
@@ -129,6 +129,7 @@ enum {
OPTION_SPICE_DH_FILE,
OPTION_SPICE_DEFERRED_FPS,
OPTION_SPICE_EXIT_ON_DISCONNECT,
+ OPTION_SPICE_PLAYBACK_FIFO_DIR,
#endif
OPTION_COUNT,
};
@@ -247,9 +248,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
@@ -267,6 +271,8 @@ struct _qxl_screen_t
} guest_primary;
uint32_t deferred_fps;
+
+ char playback_fifo_dir[PATH_MAX];
#endif /* XSPICE */
};
diff --git a/src/qxl_driver.c b/src/qxl_driver.c
index 5f70519..d165afd 100644
--- a/src/qxl_driver.c
+++ b/src/qxl_driver.c
@@ -57,6 +57,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);
@@ -129,6 +130,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 }
@@ -1724,6 +1727,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)
@@ -2367,7 +2371,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.
*/
@@ -2439,6 +2446,15 @@ qxl_pre_init (ScrnInfoPtr pScrn, int flags)
qxl->enable_image_cache ? "Enabled" : "Disabled");
xf86DrvMsg (scrnIndex, X_INFO, "Fallback Cache: %s\n",
qxl->enable_fallback_cache ? "Enabled" : "Disabled");
+
+#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
--
1.8.1.2
More information about the Spice-devel
mailing list