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