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