[PATCH] Implement sending audio to the client from a directory of FIFO queues

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.
---

A couple of small concerns worth talking about:

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.

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.

 src/Makefile.am      |   2 +
 src/qxl.h            |   6 +
 src/qxl_driver.c     |  18 ++-
 src/spiceqxl_audio.c | 318 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/spiceqxl_audio.h |  31 +++++
 5 files changed, 374 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..2e2b0b3
--- /dev/null
+++ b/src/spiceqxl_audio.c
@@ -0,0 +1,318 @@
+/*
+ * 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

[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]     [Monitors]