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