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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel