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