• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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