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
timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata)65 static 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
free_entry(pa_scache_entry *e)77 static 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
scache_add_item(pa_core *c, const char *name, bool *new_sample)92 static 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
pa_scache_add_item( pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, pa_proplist *p, uint32_t *idx)144 int 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
pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx)207 int 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
pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx)242 int 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
pa_scache_remove_item(pa_core *c, const char *name)276 int 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
pa_scache_free_all(pa_core *c)294 void 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
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)305 int 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
377 fail:
378 pa_proplist_free(merged);
379 return -1;
380 }
381
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)382 int 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
pa_scache_get_name_by_id(pa_core *c, uint32_t id)394 const 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
pa_scache_get_id_by_name(pa_core *c, const char *name)406 uint32_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
pa_scache_total_size(pa_core *c)418 size_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
pa_scache_unload_unused(pa_core *c)435 void 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
add_file(pa_core *c, const char *pathname)462 static 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
pa_scache_add_directory_lazy(pa_core *c, const char *pathname)482 int 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