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