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
56struct fifo_data {
57    char *buffer;
58    int   size;
59    int   len;
60    int   add_to;
61    int   fd;
62    SpiceWatch *watch;
63};
64
65struct 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.  */
85static 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
91static 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
108static 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
129static 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() */
157static 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() */
188static 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() */
210static 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
224static 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() */
253static 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
275static void start_watching(qxl_screen_t *qxl);
276static 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() */
355static 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() */
370static 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)
389static 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
428static 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
459static const SpicePlaybackInterface playback_sif = {
460    {
461        SPICE_INTERFACE_PLAYBACK,
462        "playback",
463        SPICE_INTERFACE_PLAYBACK_MAJOR,
464        SPICE_INTERFACE_PLAYBACK_MINOR
465    }
466};
467
468static 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
492int
493qxl_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