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