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
228 if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk, p) < 0) {
229 pa_proplist_free(p);
230 return -1;
231 }
232
233 r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx);
234 pa_memblock_unref(chunk.memblock);
235 pa_proplist_free(p);
236
237 return r;
238 }
239
pa_scache_add_file_lazy(pa_core * c,const char * name,const char * filename,uint32_t * idx)240 int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
241 pa_scache_entry *e;
242 bool new_sample;
243
244 #ifdef OS_IS_WIN32
245 char buf[MAX_PATH];
246
247 if (ExpandEnvironmentStrings(filename, buf, MAX_PATH))
248 filename = buf;
249 #endif
250
251 pa_assert(c);
252 pa_assert(name);
253 pa_assert(filename);
254
255 if (!(e = scache_add_item(c, name, &new_sample)))
256 return -1;
257
258 e->lazy = true;
259 e->filename = pa_xstrdup(filename);
260
261 pa_proplist_sets(e->proplist, PA_PROP_MEDIA_FILENAME, filename);
262
263 if (!c->scache_auto_unload_event)
264 c->scache_auto_unload_event = pa_core_rttime_new(c, pa_rtclock_now() + UNLOAD_POLL_TIME, timeout_callback, c);
265
266 if (idx)
267 *idx = e->index;
268
269 pa_hook_fire(&e->core->hooks[new_sample ? PA_CORE_HOOK_SAMPLE_CACHE_NEW : PA_CORE_HOOK_SAMPLE_CACHE_CHANGED], e);
270
271 return 0;
272 }
273
pa_scache_remove_item(pa_core * c,const char * name)274 int pa_scache_remove_item(pa_core *c, const char *name) {
275 pa_scache_entry *e;
276
277 pa_assert(c);
278 pa_assert(name);
279
280 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE)))
281 return -1;
282
283 pa_assert_se(pa_idxset_remove_by_data(c->scache, e, NULL) == e);
284
285 pa_log_debug("Removed sample \"%s\"", name);
286
287 free_entry(e);
288
289 return 0;
290 }
291
pa_scache_free_all(pa_core * c)292 void pa_scache_free_all(pa_core *c) {
293 pa_assert(c);
294
295 pa_idxset_remove_all(c->scache, (pa_free_cb_t) free_entry);
296
297 if (c->scache_auto_unload_event) {
298 c->mainloop->time_free(c->scache_auto_unload_event);
299 c->scache_auto_unload_event = NULL;
300 }
301 }
302
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)303 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) {
304 pa_scache_entry *e;
305 pa_cvolume r;
306 pa_proplist *merged;
307 bool pass_volume;
308
309 pa_assert(c);
310 pa_assert(name);
311 pa_assert(sink);
312
313 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE)))
314 return -1;
315
316 merged = pa_proplist_new();
317 pa_proplist_sets(merged, PA_PROP_MEDIA_NAME, name);
318 pa_proplist_sets(merged, PA_PROP_EVENT_ID, name);
319
320 if (e->lazy && !e->memchunk.memblock) {
321 pa_channel_map old_channel_map = e->channel_map;
322
323 if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, merged) < 0)
324 goto fail;
325
326 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
327
328 if (e->volume_is_set) {
329 if (pa_cvolume_valid(&e->volume))
330 pa_cvolume_remap(&e->volume, &old_channel_map, &e->channel_map);
331 else
332 pa_cvolume_reset(&e->volume, e->sample_spec.channels);
333 }
334 }
335
336 if (!e->memchunk.memblock)
337 goto fail;
338
339 pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name);
340
341 pass_volume = true;
342
343 if (e->volume_is_set && PA_VOLUME_IS_VALID(volume)) {
344 pa_cvolume_set(&r, e->sample_spec.channels, volume);
345 pa_sw_cvolume_multiply(&r, &r, &e->volume);
346 } else if (e->volume_is_set)
347 r = e->volume;
348 else if (PA_VOLUME_IS_VALID(volume))
349 pa_cvolume_set(&r, e->sample_spec.channels, volume);
350 else
351 pass_volume = false;
352
353 pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist);
354
355 if (p)
356 pa_proplist_update(merged, PA_UPDATE_REPLACE, p);
357
358 if (pa_play_memchunk(sink,
359 &e->sample_spec, &e->channel_map,
360 &e->memchunk,
361 pass_volume ? &r : NULL,
362 merged,
363 PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND, sink_input_idx) < 0)
364 goto fail;
365
366 pa_proplist_free(merged);
367
368 if (e->lazy)
369 time(&e->last_used_time);
370
371 return 0;
372
373 fail:
374 pa_proplist_free(merged);
375 return -1;
376 }
377
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)378 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) {
379 pa_sink *sink;
380
381 pa_assert(c);
382 pa_assert(name);
383
384 if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK)))
385 return -1;
386
387 return pa_scache_play_item(c, name, sink, volume, p, sink_input_idx);
388 }
389
pa_scache_get_name_by_id(pa_core * c,uint32_t id)390 const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id) {
391 pa_scache_entry *e;
392
393 pa_assert(c);
394 pa_assert(id != PA_IDXSET_INVALID);
395
396 if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id)))
397 return NULL;
398
399 return e->name;
400 }
401
pa_scache_get_id_by_name(pa_core * c,const char * name)402 uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) {
403 pa_scache_entry *e;
404
405 pa_assert(c);
406 pa_assert(name);
407
408 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE)))
409 return PA_IDXSET_INVALID;
410
411 return e->index;
412 }
413
pa_scache_total_size(pa_core * c)414 size_t pa_scache_total_size(pa_core *c) {
415 pa_scache_entry *e;
416 uint32_t idx;
417 size_t sum = 0;
418
419 pa_assert(c);
420
421 if (!c->scache || !pa_idxset_size(c->scache))
422 return 0;
423
424 PA_IDXSET_FOREACH(e, c->scache, idx)
425 if (e->memchunk.memblock)
426 sum += e->memchunk.length;
427
428 return sum;
429 }
430
pa_scache_unload_unused(pa_core * c)431 void pa_scache_unload_unused(pa_core *c) {
432 pa_scache_entry *e;
433 time_t now;
434 uint32_t idx;
435
436 pa_assert(c);
437
438 if (!c->scache || !pa_idxset_size(c->scache))
439 return;
440
441 time(&now);
442
443 PA_IDXSET_FOREACH(e, c->scache, idx) {
444
445 if (!e->lazy || !e->memchunk.memblock)
446 continue;
447
448 if (e->last_used_time + c->scache_idle_time > now)
449 continue;
450
451 pa_memblock_unref(e->memchunk.memblock);
452 pa_memchunk_reset(&e->memchunk);
453
454 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
455 }
456 }
457
add_file(pa_core * c,const char * pathname)458 static void add_file(pa_core *c, const char *pathname) {
459 struct stat st;
460 const char *e;
461
462 pa_core_assert_ref(c);
463 pa_assert(pathname);
464
465 e = pa_path_get_filename(pathname);
466
467 if (stat(pathname, &st) < 0) {
468 pa_log("stat('%s'): %s", pathname, pa_cstrerror(errno));
469 return;
470 }
471
472 #if defined(S_ISREG) && defined(S_ISLNK)
473 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
474 #endif
475 pa_scache_add_file_lazy(c, e, pathname, NULL);
476 }
477
pa_scache_add_directory_lazy(pa_core * c,const char * pathname)478 int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) {
479 DIR *dir;
480
481 pa_core_assert_ref(c);
482 pa_assert(pathname);
483
484 /* First try to open this as directory */
485 if (!(dir = opendir(pathname))) {
486 #ifdef HAVE_GLOB_H
487 glob_t p;
488 unsigned int i;
489 /* If that fails, try to open it as shell glob */
490
491 if (glob(pathname, GLOB_ERR|GLOB_NOSORT, NULL, &p) < 0) {
492 pa_log("failed to open directory '%s': %s", pathname, pa_cstrerror(errno));
493 return -1;
494 }
495
496 for (i = 0; i < p.gl_pathc; i++)
497 add_file(c, p.gl_pathv[i]);
498
499 globfree(&p);
500 #else
501 return -1;
502 #endif
503 } else {
504 struct dirent *e;
505
506 while ((e = readdir(dir))) {
507 char *p;
508
509 if (e->d_name[0] == '.')
510 continue;
511
512 p = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", pathname, e->d_name);
513 add_file(c, p);
514 pa_xfree(p);
515 }
516
517 closedir(dir);
518 }
519
520 return 0;
521 }
522