1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2004-2008 Lennart Poettering 5 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB 6 7 PulseAudio is free software; you can redistribute it and/or modify 8 it under the terms of the GNU Lesser General Public License as published 9 by the Free Software Foundation; either version 2.1 of the License, 10 or (at your option) any later version. 11 12 PulseAudio is distributed in the hope that it will be useful, but 13 WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 General Public License for more details. 16 17 You should have received a copy of the GNU Lesser General Public License 18 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 19***/ 20 21#ifdef HAVE_CONFIG_H 22#include <config.h> 23#endif 24 25#include <stdlib.h> 26#include <stdio.h> 27#include <sys/types.h> 28#include <dirent.h> 29#include <sys/stat.h> 30#include <errno.h> 31#include <limits.h> 32#include <time.h> 33 34#ifdef HAVE_GLOB_H 35#include <glob.h> 36#endif 37 38#ifdef HAVE_WINDOWS_H 39#include <windows.h> 40#endif 41 42#include <pulse/mainloop.h> 43#include <pulse/channelmap.h> 44#include <pulse/timeval.h> 45#include <pulse/util.h> 46#include <pulse/volume.h> 47#include <pulse/xmalloc.h> 48#include <pulse/rtclock.h> 49 50#include <pulsecore/sink-input.h> 51#include <pulsecore/play-memchunk.h> 52#include <pulsecore/core-subscribe.h> 53#include <pulsecore/namereg.h> 54#include <pulsecore/sound-file.h> 55#include <pulsecore/core-rtclock.h> 56#include <pulsecore/core-util.h> 57#include <pulsecore/log.h> 58#include <pulsecore/core-error.h> 59#include <pulsecore/macro.h> 60 61#include "core-scache.h" 62 63#define UNLOAD_POLL_TIME (60 * PA_USEC_PER_SEC) 64 65static void timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { 66 pa_core *c = userdata; 67 68 pa_assert(c); 69 pa_assert(c->mainloop == m); 70 pa_assert(c->scache_auto_unload_event == e); 71 72 pa_scache_unload_unused(c); 73 74 pa_core_rttime_restart(c, e, pa_rtclock_now() + UNLOAD_POLL_TIME); 75} 76 77static void free_entry(pa_scache_entry *e) { 78 pa_assert(e); 79 80 pa_namereg_unregister(e->core, e->name); 81 pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_REMOVE, e->index); 82 pa_hook_fire(&e->core->hooks[PA_CORE_HOOK_SAMPLE_CACHE_UNLINK], e); 83 pa_xfree(e->name); 84 pa_xfree(e->filename); 85 if (e->memchunk.memblock) 86 pa_memblock_unref(e->memchunk.memblock); 87 if (e->proplist) 88 pa_proplist_free(e->proplist); 89 pa_xfree(e); 90} 91 92static pa_scache_entry* scache_add_item(pa_core *c, const char *name, bool *new_sample) { 93 pa_scache_entry *e; 94 95 pa_assert(c); 96 pa_assert(name); 97 pa_assert(new_sample); 98 99 if ((e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) { 100 if (e->memchunk.memblock) 101 pa_memblock_unref(e->memchunk.memblock); 102 103 pa_xfree(e->filename); 104 pa_proplist_clear(e->proplist); 105 106 pa_assert(e->core == c); 107 108 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); 109 *new_sample = false; 110 } else { 111 e = pa_xnew(pa_scache_entry, 1); 112 113 if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, true)) { 114 pa_xfree(e); 115 return NULL; 116 } 117 118 e->name = pa_xstrdup(name); 119 e->core = c; 120 e->proplist = pa_proplist_new(); 121 122 pa_idxset_put(c->scache, e, &e->index); 123 124 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_NEW, e->index); 125 *new_sample = true; 126 } 127 128 e->last_used_time = 0; 129 pa_memchunk_reset(&e->memchunk); 130 e->filename = NULL; 131 e->lazy = false; 132 e->last_used_time = 0; 133 134 pa_sample_spec_init(&e->sample_spec); 135 pa_channel_map_init(&e->channel_map); 136 pa_cvolume_init(&e->volume); 137 e->volume_is_set = false; 138 139 pa_proplist_sets(e->proplist, PA_PROP_MEDIA_ROLE, "event"); 140 141 return e; 142} 143 144int pa_scache_add_item( 145 pa_core *c, 146 const char *name, 147 const pa_sample_spec *ss, 148 const pa_channel_map *map, 149 const pa_memchunk *chunk, 150 pa_proplist *p, 151 uint32_t *idx) { 152 153 pa_scache_entry *e; 154 char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; 155 pa_channel_map tmap; 156 bool new_sample; 157 158 pa_assert(c); 159 pa_assert(name); 160 pa_assert(!ss || pa_sample_spec_valid(ss)); 161 pa_assert(!map || (pa_channel_map_valid(map) && ss && pa_channel_map_compatible(map, ss))); 162 163 if (ss && !map) { 164 pa_channel_map_init_extend(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT); 165 map = &tmap; 166 } 167 168 if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX) 169 return -1; 170 171 if (!(e = scache_add_item(c, name, &new_sample))) 172 return -1; 173 174 pa_sample_spec_init(&e->sample_spec); 175 pa_channel_map_init(&e->channel_map); 176 pa_cvolume_init(&e->volume); 177 e->volume_is_set = false; 178 179 if (ss) { 180 e->sample_spec = *ss; 181 pa_cvolume_reset(&e->volume, ss->channels); 182 } 183 184 if (map) 185 e->channel_map = *map; 186 187 if (chunk) { 188 e->memchunk = *chunk; 189 pa_memblock_ref(e->memchunk.memblock); 190 } 191 192 if (p) 193 pa_proplist_update(e->proplist, PA_UPDATE_REPLACE, p); 194 195 if (idx) 196 *idx = e->index; 197 198 pa_log_debug("Created sample \"%s\" (#%d), %lu bytes with sample spec %s", 199 name, e->index, (unsigned long) e->memchunk.length, 200 pa_sample_spec_snprint(st, sizeof(st), &e->sample_spec)); 201 202 pa_hook_fire(&e->core->hooks[new_sample ? PA_CORE_HOOK_SAMPLE_CACHE_NEW : PA_CORE_HOOK_SAMPLE_CACHE_CHANGED], e); 203 204 return 0; 205} 206 207int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx) { 208 pa_sample_spec ss; 209 pa_channel_map map; 210 pa_memchunk chunk; 211 int r; 212 pa_proplist *p; 213 214#ifdef OS_IS_WIN32 215 char buf[MAX_PATH]; 216 217 if (ExpandEnvironmentStrings(filename, buf, MAX_PATH)) 218 filename = buf; 219#endif 220 221 pa_assert(c); 222 pa_assert(name); 223 pa_assert(filename); 224 225 p = pa_proplist_new(); 226 pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename); 227#ifdef SNDFILE_ENABLE 228 if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk, p) < 0) { 229 pa_proplist_free(p); 230 return -1; 231 } 232#else 233 return -1; 234#endif 235 r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx); 236 pa_memblock_unref(chunk.memblock); 237 pa_proplist_free(p); 238 239 return r; 240} 241 242int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) { 243 pa_scache_entry *e; 244 bool new_sample; 245 246#ifdef OS_IS_WIN32 247 char buf[MAX_PATH]; 248 249 if (ExpandEnvironmentStrings(filename, buf, MAX_PATH)) 250 filename = buf; 251#endif 252 253 pa_assert(c); 254 pa_assert(name); 255 pa_assert(filename); 256 257 if (!(e = scache_add_item(c, name, &new_sample))) 258 return -1; 259 260 e->lazy = true; 261 e->filename = pa_xstrdup(filename); 262 263 pa_proplist_sets(e->proplist, PA_PROP_MEDIA_FILENAME, filename); 264 265 if (!c->scache_auto_unload_event) 266 c->scache_auto_unload_event = pa_core_rttime_new(c, pa_rtclock_now() + UNLOAD_POLL_TIME, timeout_callback, c); 267 268 if (idx) 269 *idx = e->index; 270 271 pa_hook_fire(&e->core->hooks[new_sample ? PA_CORE_HOOK_SAMPLE_CACHE_NEW : PA_CORE_HOOK_SAMPLE_CACHE_CHANGED], e); 272 273 return 0; 274} 275 276int pa_scache_remove_item(pa_core *c, const char *name) { 277 pa_scache_entry *e; 278 279 pa_assert(c); 280 pa_assert(name); 281 282 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) 283 return -1; 284 285 pa_assert_se(pa_idxset_remove_by_data(c->scache, e, NULL) == e); 286 287 pa_log_debug("Removed sample \"%s\"", name); 288 289 free_entry(e); 290 291 return 0; 292} 293 294void pa_scache_free_all(pa_core *c) { 295 pa_assert(c); 296 297 pa_idxset_remove_all(c->scache, (pa_free_cb_t) free_entry); 298 299 if (c->scache_auto_unload_event) { 300 c->mainloop->time_free(c->scache_auto_unload_event); 301 c->scache_auto_unload_event = NULL; 302 } 303} 304 305int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { 306 pa_scache_entry *e; 307 pa_cvolume r; 308 pa_proplist *merged; 309 bool pass_volume; 310 311 pa_assert(c); 312 pa_assert(name); 313 pa_assert(sink); 314 315 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) 316 return -1; 317 318 merged = pa_proplist_new(); 319 pa_proplist_sets(merged, PA_PROP_MEDIA_NAME, name); 320 pa_proplist_sets(merged, PA_PROP_EVENT_ID, name); 321 322 if (e->lazy && !e->memchunk.memblock) { 323 pa_channel_map old_channel_map = e->channel_map; 324#ifdef SNDFILE_ENABLE 325 if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, merged) < 0) 326 goto fail; 327#else 328 goto fail; 329#endif 330 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); 331 332 if (e->volume_is_set) { 333 if (pa_cvolume_valid(&e->volume)) 334 pa_cvolume_remap(&e->volume, &old_channel_map, &e->channel_map); 335 else 336 pa_cvolume_reset(&e->volume, e->sample_spec.channels); 337 } 338 } 339 340 if (!e->memchunk.memblock) 341 goto fail; 342 343 pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name); 344 345 pass_volume = true; 346 347 if (e->volume_is_set && PA_VOLUME_IS_VALID(volume)) { 348 pa_cvolume_set(&r, e->sample_spec.channels, volume); 349 pa_sw_cvolume_multiply(&r, &r, &e->volume); 350 } else if (e->volume_is_set) 351 r = e->volume; 352 else if (PA_VOLUME_IS_VALID(volume)) 353 pa_cvolume_set(&r, e->sample_spec.channels, volume); 354 else 355 pass_volume = false; 356 357 pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist); 358 359 if (p) 360 pa_proplist_update(merged, PA_UPDATE_REPLACE, p); 361 362 if (pa_play_memchunk(sink, 363 &e->sample_spec, &e->channel_map, 364 &e->memchunk, 365 pass_volume ? &r : NULL, 366 merged, 367 PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND, sink_input_idx) < 0) 368 goto fail; 369 370 pa_proplist_free(merged); 371 372 if (e->lazy) 373 time(&e->last_used_time); 374 375 return 0; 376 377fail: 378 pa_proplist_free(merged); 379 return -1; 380} 381 382int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { 383 pa_sink *sink; 384 385 pa_assert(c); 386 pa_assert(name); 387 388 if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK))) 389 return -1; 390 391 return pa_scache_play_item(c, name, sink, volume, p, sink_input_idx); 392} 393 394const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id) { 395 pa_scache_entry *e; 396 397 pa_assert(c); 398 pa_assert(id != PA_IDXSET_INVALID); 399 400 if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id))) 401 return NULL; 402 403 return e->name; 404} 405 406uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) { 407 pa_scache_entry *e; 408 409 pa_assert(c); 410 pa_assert(name); 411 412 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) 413 return PA_IDXSET_INVALID; 414 415 return e->index; 416} 417 418size_t pa_scache_total_size(pa_core *c) { 419 pa_scache_entry *e; 420 uint32_t idx; 421 size_t sum = 0; 422 423 pa_assert(c); 424 425 if (!c->scache || !pa_idxset_size(c->scache)) 426 return 0; 427 428 PA_IDXSET_FOREACH(e, c->scache, idx) 429 if (e->memchunk.memblock) 430 sum += e->memchunk.length; 431 432 return sum; 433} 434 435void pa_scache_unload_unused(pa_core *c) { 436 pa_scache_entry *e; 437 time_t now; 438 uint32_t idx; 439 440 pa_assert(c); 441 442 if (!c->scache || !pa_idxset_size(c->scache)) 443 return; 444 445 time(&now); 446 447 PA_IDXSET_FOREACH(e, c->scache, idx) { 448 449 if (!e->lazy || !e->memchunk.memblock) 450 continue; 451 452 if (e->last_used_time + c->scache_idle_time > now) 453 continue; 454 455 pa_memblock_unref(e->memchunk.memblock); 456 pa_memchunk_reset(&e->memchunk); 457 458 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); 459 } 460} 461 462static void add_file(pa_core *c, const char *pathname) { 463 struct stat st; 464 const char *e; 465 466 pa_core_assert_ref(c); 467 pa_assert(pathname); 468 469 e = pa_path_get_filename(pathname); 470 471 if (stat(pathname, &st) < 0) { 472 pa_log("stat('%s'): %s", pathname, pa_cstrerror(errno)); 473 return; 474 } 475 476#if defined(S_ISREG) && defined(S_ISLNK) 477 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) 478#endif 479 pa_scache_add_file_lazy(c, e, pathname, NULL); 480} 481 482int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) { 483 DIR *dir; 484 485 pa_core_assert_ref(c); 486 pa_assert(pathname); 487 488 /* First try to open this as directory */ 489 if (!(dir = opendir(pathname))) { 490#ifdef HAVE_GLOB_H 491 glob_t p; 492 unsigned int i; 493 /* If that fails, try to open it as shell glob */ 494 495 if (glob(pathname, GLOB_ERR|GLOB_NOSORT, NULL, &p) < 0) { 496 pa_log("failed to open directory '%s': %s", pathname, pa_cstrerror(errno)); 497 return -1; 498 } 499 500 for (i = 0; i < p.gl_pathc; i++) 501 add_file(c, p.gl_pathv[i]); 502 503 globfree(&p); 504#else 505 return -1; 506#endif 507 } else { 508 struct dirent *e; 509 510 while ((e = readdir(dir))) { 511 char *p; 512 513 if (e->d_name[0] == '.') 514 continue; 515 516 p = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", pathname, e->d_name); 517 add_file(c, p); 518 pa_xfree(p); 519 } 520 521 closedir(dir); 522 } 523 524 return 0; 525} 526