[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