Home | History | Annotate | Line # | Download | only in src
      1 /*
      2  * Copyright 2012 Andrew Eikum for CodeWeavers Inc.
      3  *
      4  * Permission is hereby granted, free of charge, to any person obtaining a
      5  * copy of this software and associated documentation files (the "Software"),
      6  * to deal in the Software without restriction, including without limitation
      7  * on the rights to use, copy, modify, merge, publish, distribute, sub
      8  * license, and/or sell copies of the Software, and to permit persons to whom
      9  * the Software is furnished to do so, subject to the following conditions:
     10  *
     11  * The above copyright notice and this permission notice (including the next
     12  * paragraph) shall be included in all copies or substantial portions of the
     13  * Software.
     14  *
     15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     17  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
     18  * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     19  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     20  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21  */
     22 
     23 /* XSpice based audio feed; reads from files (presumably fifos) in a configured directory,
     24    and mixes their raw data on to the spice playback channel.  */
     25 
     26 
     27 #ifdef HAVE_CONFIG_H
     28 #include "config.h"
     29 #endif
     30 
     31 #include "spiceqxl_audio.h"
     32 
     33 #include <errno.h>
     34 #include <fcntl.h>
     35 #include <pthread.h>
     36 #include <sys/time.h>
     37 #include <unistd.h>
     38 #include <dirent.h>
     39 #if defined(HAVE_SYS_INOTIFY_H)
     40 #include <sys/inotify.h>
     41 #endif
     42 
     43 /* mplayer + pulse will write data to the fifo as fast as we can read it.
     44        So we need to pace both how quickly we consume the data and how quickly
     45        we feed the data in to Spice.  We will read ahead (up to READ_BUFFER_PERIODS),
     46        and feed ahead into the Spice server (up to FEED_BUFFER_PERIODS).
     47 */
     48 
     49 #define IDLE_MS              300
     50 #define PERIOD_MS            10
     51 #define READ_BUFFER_PERIODS  2
     52 #define FEED_BUFFER_PERIODS  8
     53 
     54 #define MAX_FIFOS 16
     55 
     56 struct fifo_data {
     57     char *buffer;
     58     int   size;
     59     int   len;
     60     int   add_to;
     61     int   fd;
     62     SpiceWatch *watch;
     63 };
     64 
     65 struct audio_data {
     66     struct fifo_data fifos[MAX_FIFOS];
     67     int active;
     68     uint32_t *spice_buffer;
     69     int spice_buffer_bytes;
     70     int period_bytes;
     71     struct timeval fed_through_time;
     72     int remainder;
     73     int fifo_count;
     74     int closed_fifos;
     75     SpiceTimer *wall_timer;
     76     int wall_timer_type;
     77     int dir_watch;
     78     int fifo_dir_watch;
     79     SpiceWatch *fifo_dir_qxl_watch;
     80 };
     81 
     82 /* We maintain a ring buffer for each file we are reading from;
     83    these helper functions facilitate adding data to the buffer,
     84    and removing it.  */
     85 static inline void fifo_data_added(struct fifo_data *f, int n)
     86 {
     87     f->add_to = (f->add_to + n) % f->size;
     88     f->len += n;
     89 }
     90 
     91 static inline int fifo_read(struct fifo_data *f)
     92 {
     93     int rc;
     94     int len = min(f->size - f->len, f->size - f->add_to);
     95     rc = read(f->fd, f->buffer + f->add_to, len);
     96     if (rc > 0)
     97         fifo_data_added(f, rc);
     98 
     99     if (rc > 0 && rc == len && f->size - f->len > 0) {
    100         rc = read(f->fd, f->buffer + f->add_to, f->size - f->len);
    101         if (rc > 0)
    102             fifo_data_added(f, rc);
    103     }
    104 
    105     return rc;
    106 }
    107 
    108 static inline void fifo_remove_data(struct fifo_data *f, unsigned char *dest, int len)
    109 {
    110     int remove_from = f->add_to >= f->len ? f->add_to - f->len : f->add_to + f->size - f->len;
    111     int remain = f->size - remove_from;
    112 
    113     if (remain < len) {
    114         if (dest) {
    115             memcpy(dest, f->buffer + remove_from, remain);
    116             dest += remain;
    117         }
    118         len -= remain;
    119         f->len -= remain;
    120         remove_from = 0;
    121     }
    122 
    123     if (dest) {
    124         memcpy(dest, f->buffer + remove_from, len);
    125     }
    126     f->len -= len;
    127 }
    128 
    129 static void mix_in_one_fifo(struct fifo_data *f, int16_t *out, int len)
    130 {
    131     int s;
    132     int16_t *in;
    133 
    134     if (len > f->len)
    135         len = f->len;
    136 
    137     in = calloc(1, len);
    138 
    139     fifo_remove_data(f, (unsigned char *) in, len);
    140 
    141     for (s = 0; s < (len / sizeof(int16_t)); s++) {
    142         /* FIXME: Ehhh, this'd be better as floats. With this algorithm,
    143          * samples mixed after being clipped will have undue weight. But
    144          * if we're clipping, then we're distorted anyway, so whatever. */
    145         if (out[s] + in[s] > INT16_MAX)
    146             out[s] = INT16_MAX;
    147         else if (out[s] + in[s] < -INT16_MAX)
    148             out[s] = -INT16_MAX;
    149         else
    150             out[s] += in[s];
    151     }
    152 
    153     free(in);
    154 }
    155 
    156 /* a helper for process_fifos() */
    157 static void mix_in_fifos(qxl_screen_t *qxl)
    158 {
    159     int i;
    160     struct audio_data *data = qxl->playback_opaque;
    161     struct fifo_data *f;
    162 
    163     if (data->spice_buffer) {
    164         memset(data->spice_buffer, 0, data->spice_buffer_bytes);
    165     }
    166 
    167     if (data->fifo_count == 0)
    168         return;
    169 
    170     /* First fifo can just be copied */
    171     f = &data->fifos[0];
    172     fifo_remove_data(f, (unsigned char *) data->spice_buffer, min(data->spice_buffer_bytes, f->len));
    173 
    174     /* Extra fifos need to be mixed in */
    175     for (i = 1; i < data->fifo_count; i++) {
    176         f = &data->fifos[i];
    177         if (f->len > 0) {
    178             if (data->spice_buffer) {
    179                 mix_in_one_fifo(f, (int16_t *) data->spice_buffer, data->spice_buffer_bytes);
    180             } else {
    181                 fifo_remove_data(f, NULL, min(data->spice_buffer_bytes, f->len));
    182             }
    183         }
    184     }
    185 }
    186 
    187 /* a helper for process_fifos() */
    188 static int can_feed(struct audio_data *data)
    189 {
    190     struct timeval end, diff;
    191 
    192     gettimeofday(&end, NULL);
    193 
    194     if (end.tv_sec > data->fed_through_time.tv_sec ||
    195         (end.tv_sec == data->fed_through_time.tv_sec &&
    196          end.tv_usec >= data->fed_through_time.tv_usec)) {
    197         data->fed_through_time.tv_sec = data->fed_through_time.tv_usec = 0;
    198         data->remainder = 0;
    199         return 1;
    200     }
    201 
    202     timersub(&data->fed_through_time, &end, &diff);
    203     if (diff.tv_sec == 0 && diff.tv_usec < PERIOD_MS * 1000 * FEED_BUFFER_PERIODS)
    204         return 1;
    205 
    206     return 0;
    207 }
    208 
    209 /* a helper for process_fifos() */
    210 static void did_feed(struct audio_data *data, int len)
    211 {
    212     struct timeval diff;
    213 
    214     if (data->fed_through_time.tv_sec == 0 && data->fed_through_time.tv_usec == 0)
    215         gettimeofday(&data->fed_through_time, NULL);
    216 
    217     diff.tv_sec = 0;
    218     diff.tv_usec = (data->remainder + (len * PERIOD_MS * 1000)) / data->period_bytes;
    219     data->remainder = (data->remainder + (len * PERIOD_MS * 1000)) % data->period_bytes;
    220 
    221     timeradd(&data->fed_through_time, &diff, &data->fed_through_time);
    222 }
    223 
    224 static int process_fifos(qxl_screen_t *qxl, struct audio_data *data, int maxlen)
    225 {
    226     while (maxlen > 0) {
    227         if (! data->spice_buffer) {
    228             uint32_t chunk_frames;
    229             spice_server_playback_get_buffer(&qxl->playback_sin, &data->spice_buffer, &chunk_frames);
    230             data->spice_buffer_bytes = data->spice_buffer ?
    231                 chunk_frames * sizeof(int16_t) * SPICE_INTERFACE_PLAYBACK_CHAN :
    232                 data->period_bytes * READ_BUFFER_PERIODS;
    233         }
    234 
    235         if (! can_feed(data)) {
    236             return FALSE;
    237         }
    238 
    239         mix_in_fifos(qxl);
    240 
    241         did_feed(data, data->spice_buffer_bytes);
    242         maxlen -= data->spice_buffer_bytes;
    243 
    244         if (data->spice_buffer) {
    245             spice_server_playback_put_samples(&qxl->playback_sin, data->spice_buffer);
    246             data->spice_buffer = NULL;
    247         }
    248     }
    249     return TRUE;
    250 }
    251 
    252 /* a helper for read_from_fifos() */
    253 static void condense_fifos(qxl_screen_t *qxl)
    254 {
    255     struct audio_data *data = qxl->playback_opaque;
    256     int i;
    257 
    258     for (i = 0; i < data->fifo_count; i++) {
    259         struct fifo_data *f = &data->fifos[i];
    260         if (f->fd == -1 && f->len == 0) {
    261             if ((i + 1) < data->fifo_count) {
    262                 struct fifo_data tmp = *f;
    263                 *f = data->fifos[data->fifo_count - 1];
    264                 data->fifos[data->fifo_count - 1] = tmp;
    265             }
    266             data->fifo_count--;
    267             i--;
    268             if (!--data->closed_fifos) {
    269                 break;
    270             }
    271         }
    272     }
    273 }
    274 
    275 static void start_watching(qxl_screen_t *qxl);
    276 static void read_from_fifos(int fd, int event, void *opaque)
    277 {
    278     qxl_screen_t *qxl = opaque;
    279     struct audio_data *data = qxl->playback_opaque;
    280     int i;
    281     int maxlen = 0;
    282 
    283     if (data->wall_timer_type) {
    284         qxl->core->timer_cancel(data->wall_timer);
    285         data->wall_timer_type = 0;
    286     }
    287 
    288     for (i = 0; i < data->fifo_count; i++) {
    289         struct fifo_data *f = &data->fifos[i];
    290 
    291         if (f->size - f->len > 0 && f->fd >= 0) {
    292             int rc;
    293 
    294             rc = fifo_read(f);
    295             if (rc == -1 && (errno == EAGAIN || errno == EINTR))
    296                 /* no new data to read */;
    297             else if (rc <= 0) {
    298                 if (rc == 0)
    299                     ErrorF("fifo %d closed\n", f->fd);
    300                 else
    301                     ErrorF("fifo %d error %d: %s\n", f->fd, errno, strerror(errno));
    302 
    303                 if (f->watch)
    304                     qxl->core->watch_remove(f->watch);
    305                 f->watch = NULL;
    306                 close(f->fd);
    307                 f->fd = -1;
    308                 /* Setting closed_fifos will only have an effect once
    309                  * the closed fifo's buffer is empty.
    310                  */
    311                 data->closed_fifos++;
    312             }
    313 
    314             if (f->size == f->len) {
    315                 if (f->watch)
    316                     qxl->core->watch_remove(f->watch);
    317                 f->watch = NULL;
    318             }
    319         }
    320 
    321         if (f->len > maxlen)
    322             maxlen = f->len;
    323     }
    324 
    325     if (data->closed_fifos) {
    326         condense_fifos(qxl);
    327     }
    328 
    329     if (maxlen && !data->active) {
    330         spice_server_playback_start(&qxl->playback_sin);
    331         data->active = 1;
    332     }
    333 
    334     if (!process_fifos(qxl, data, maxlen)) {
    335         /* There is still some fifo data to process */
    336         qxl->core->timer_start(data->wall_timer, PERIOD_MS);
    337         data->wall_timer_type = PERIOD_MS;
    338 
    339     } else if (data->fifo_count) {
    340         /* All the fifo data was processed. Wait for more */
    341         start_watching(qxl);
    342 
    343         /* But none may arrive so stop processing if that happens */
    344         qxl->core->timer_start(data->wall_timer, IDLE_MS);
    345         data->wall_timer_type = IDLE_MS;
    346 
    347     } else if (data->active) {
    348         /* There is no open fifo anymore */
    349         spice_server_playback_stop(&qxl->playback_sin);
    350         data->active = 0;
    351     }
    352 }
    353 
    354 /* a helper for read_from_fifos() */
    355 static void start_watching(qxl_screen_t *qxl)
    356 {
    357     struct audio_data *data = qxl->playback_opaque;
    358     int i;
    359 
    360     for (i = 0; i < data->fifo_count; i++) {
    361         struct fifo_data *f = &data->fifos[i];
    362         if (f->watch || f->size == f->len || f->fd == -1)
    363             continue;
    364 
    365         f->watch = qxl->core->watch_add(f->fd, SPICE_WATCH_EVENT_READ, read_from_fifos, qxl);
    366     }
    367 }
    368 
    369 /* a helper for read_from_fifos() */
    370 static void wall_ticker(void *opaque)
    371 {
    372     qxl_screen_t *qxl = opaque;
    373     struct audio_data *data = qxl->playback_opaque;
    374 
    375     if (data->wall_timer_type == IDLE_MS) {
    376         /* The audio is likely paused in the application(s) */
    377         if (data->active) {
    378             spice_server_playback_stop(&qxl->playback_sin);
    379             data->active = 0;
    380         }
    381         data->wall_timer_type = 0;
    382     } else {
    383         data->wall_timer_type = 0;
    384         read_from_fifos(-1, 0, qxl);
    385     }
    386 }
    387 
    388 #if defined(HAVE_SYS_INOTIFY_H)
    389 static void handle_one_change(qxl_screen_t *qxl, struct inotify_event *e)
    390 {
    391 
    392     if (e->mask & (IN_CREATE | IN_MOVED_TO)) {
    393         struct audio_data *data = qxl->playback_opaque;
    394         struct fifo_data *f;
    395         char *fname;
    396 
    397         f = &data->fifos[data->fifo_count];
    398 
    399         if (data->fifo_count == MAX_FIFOS) {
    400             static int once = 0;
    401             if (!once) {
    402                 ErrorF("playback: Too many FIFOs already open\n");
    403                 ++once;
    404             }
    405             return;
    406         }
    407 
    408         fname = xnfalloc(strlen(e->name) + strlen(qxl->playback_fifo_dir) + 1 + 1);
    409         strcpy(fname, qxl->playback_fifo_dir);
    410         strcat(fname, "/");
    411         strcat(fname, e->name);
    412 
    413         f->fd = open(fname, O_RDONLY | O_RSYNC | O_NONBLOCK);
    414         free(fname);
    415         if (f->fd < 0) {
    416             ErrorF("playback: open FIFO '%s' failed: %s\n", e->name, strerror(errno));
    417             return;
    418         }
    419 
    420         ErrorF("playback: opened FIFO '%s' as %d:%d\n", e->name, data->fifo_count, f->fd);
    421 
    422         data->fifo_count++;
    423 
    424         f->watch = qxl->core->watch_add(f->fd, SPICE_WATCH_EVENT_READ, read_from_fifos, qxl);
    425     }
    426 }
    427 
    428 static void playback_dir_changed(int fd, int event, void *opaque)
    429 {
    430     qxl_screen_t *qxl = opaque;
    431     static unsigned char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
    432     static int offset = 0;
    433     struct inotify_event *e;
    434     int rc;
    435 
    436     do {
    437         rc = read(fd, buf + offset, sizeof(buf) - offset);
    438         if (rc > 0) {
    439             offset += rc;
    440             if (offset >= sizeof(*e)) {
    441                 int len;
    442                 e = (struct inotify_event *) buf;
    443                 len = sizeof(*e) + e->len;
    444                 if (offset >= len) {
    445                     handle_one_change(qxl, e);
    446                     if (offset > len)
    447                         memmove(buf, buf + offset, offset - len);
    448                     offset -= len;
    449                 }
    450             }
    451         }
    452     }
    453     while (rc > 0);
    454 }
    455 #endif
    456 
    457 
    458 
    459 static const SpicePlaybackInterface playback_sif = {
    460     {
    461         SPICE_INTERFACE_PLAYBACK,
    462         "playback",
    463         SPICE_INTERFACE_PLAYBACK_MAJOR,
    464         SPICE_INTERFACE_PLAYBACK_MINOR
    465     }
    466 };
    467 
    468 static void audio_initialize (qxl_screen_t *qxl)
    469 {
    470     int i;
    471     struct audio_data *data = qxl->playback_opaque;
    472     int freq = SPICE_INTERFACE_PLAYBACK_FREQ;
    473     int period_frames;
    474     int frame_bytes;
    475 
    476 #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
    477     freq = spice_server_get_best_playback_rate(&qxl->playback_sin);
    478 #endif
    479 
    480     period_frames = freq * PERIOD_MS / 1000;
    481     frame_bytes = sizeof(int16_t) * SPICE_INTERFACE_PLAYBACK_CHAN;
    482     data->period_bytes = period_frames * frame_bytes;
    483 
    484     for (i = 0; i < MAX_FIFOS; ++i) {
    485         data->fifos[i].fd = -1;
    486         data->fifos[i].size = data->period_bytes * READ_BUFFER_PERIODS;
    487         data->fifos[i].buffer = calloc(1, data->fifos[i].size);
    488     }
    489 }
    490 
    491 
    492 int
    493 qxl_add_spice_playback_interface (qxl_screen_t *qxl)
    494 {
    495     int ret;
    496     struct audio_data *data = calloc(1, sizeof(*data));
    497 
    498 #if defined(HAVE_SYS_INOTIFY_H) && defined(HAVE_INOTIFY_INIT1)
    499     if (qxl->playback_fifo_dir[0] == 0) {
    500         ErrorF("playback: no audio FIFO directory, audio is disabled\n");
    501         free(data);
    502         return 0;
    503     }
    504 
    505     qxl->playback_sin.base.sif = &playback_sif.base;
    506     ret = spice_server_add_interface(qxl->spice_server, &qxl->playback_sin.base);
    507     if (ret < 0) {
    508         free(data);
    509         return errno;
    510     }
    511 
    512 #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
    513     spice_server_set_playback_rate(&qxl->playback_sin,
    514             spice_server_get_best_playback_rate(&qxl->playback_sin));
    515 #else
    516     /* disable CELT */
    517     ret = spice_server_set_playback_compression(qxl->spice_server, 0);
    518     if (ret < 0) {
    519         free(data);
    520         return errno;
    521     }
    522 #endif
    523 
    524 
    525     qxl->playback_opaque = data;
    526     audio_initialize(qxl);
    527 
    528     data->wall_timer = qxl->core->timer_add(wall_ticker, qxl);
    529 
    530     data->dir_watch = inotify_init1(IN_NONBLOCK);
    531     data->fifo_dir_watch = -1;
    532     if (data->dir_watch >= 0)
    533         data->fifo_dir_watch = inotify_add_watch(data->dir_watch, qxl->playback_fifo_dir, IN_CREATE | IN_MOVE);
    534 
    535     if (data->fifo_dir_watch == -1) {
    536         ErrorF("Error %s(%d) watching the fifo dir\n", strerror(errno), errno);
    537         return errno;
    538     }
    539 
    540     data->fifo_dir_qxl_watch = qxl->core->watch_add(data->dir_watch,
    541             SPICE_WATCH_EVENT_READ, playback_dir_changed, qxl);
    542 
    543 #else
    544     ErrorF("inotify not available; audio disabled.\n");
    545 #endif
    546     return 0;
    547 }
    548