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
65static 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
77static 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
92static 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
144int 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
207int 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
242int 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
276int 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
294void 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
305int 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
377fail:
378    pa_proplist_free(merged);
379    return -1;
380}
381
382int 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
394const 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
406uint32_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
418size_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
435void 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
462static 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
482int 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