Add bluetooth sink check for enabling audio hwdec & swdec switching feature
[platform/core/multimedia/pulseaudio-modules-audio-dsp.git] / src / module-audio-dsp.c
1 /*
2  * module-audio-dsp.c -- PulseAudio module for audio-dsp
3  * Copyright (c) 2012, Intel Corporation.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms and conditions of the GNU Lesser General Public License,
7  * version 2.1, as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.
12  * See the GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston,
17  * MA 02110-1301 USA.
18  *
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <sys/ioctl.h>
30 #include <dlfcn.h>
31 #include <dirent.h>
32 #include <linux/limits.h>
33 #include <unistd.h>
34
35 #include <pulsecore/core.h>
36 #include <pulsecore/module.h>
37 #include <pulsecore/core-util.h>
38 #include <pulsecore/log.h>
39 #include <pulsecore/sink-input.h>
40 #include <pulsecore/namereg.h>
41 #include <pulsecore/modargs.h>
42 #include <vconf.h>
43
44 #define MEDIA_NAME_AUDIO_DSP "DSP PLAYBACK"
45 #define CLV_VOLUME_DEV_PATH        "/dev/intel_sst_ctrl"
46 #define SST_VOLUME_TYPE 0x602
47 #define SST_VOLUME_SIZE 1
48 #define SST_PPP_VOL_STR_ID  0x03
49 #define SNDRV_SST_SET_ALGO    _IOW('L', 0x30,  struct snd_ppp_params *)
50 #define SNDRV_SST_GET_ALGO    _IOWR('L', 0x31,  struct snd_ppp_params *)
51 #define SST_CODEC_VOLUME_CONTROL  0x67
52 #define SNDRV_SST_MUTE        _IOW('L', 0x13, struct snd_sst_mute *)
53 #define SST_VOLUME_MUTE 0xA0
54 #define BLUEZ_API           "bluez"
55
56 PA_MODULE_AUTHOR("vivian zhang");
57 PA_MODULE_DESCRIPTION("audio-dsp");
58 PA_MODULE_VERSION(PACKAGE_VERSION);
59 PA_MODULE_LOAD_ONCE(TRUE);
60
61 static void *plugin;
62 static void (*change_volume) (pa_volume_t);
63 static void (*set_mute) (int);
64
65 static int _audio_dsp_plugin_get_list(const char *plugdir ,char ***list);
66 static void _audio_dsp_plugin_destroy_list(char **list);
67 static int audio_dsp_plugin_scan(const char *plugindir);
68 static int audio_dsp_plugin_open(char *file);
69 static void audio_dsp_plugin_close();
70 static char* __strcatdup(const char *str1, const char *str2);
71
72 struct userdata
73 {
74     pa_core *core;
75     pa_module *module;
76     pa_hook_slot *sink_input_new_hook_slot;
77     pa_hook_slot *sink_input_put_hook_slot;
78     pa_hook_slot *sink_input_put_hook_slot_rt_switch;
79     pa_subscription *subscription;
80 };
81
82 /**
83  * @brief audio hardware decoder & software decoder switch
84  */
85 #define VCONFKEY_AUDIODEC_SWITCH "memory/sound/audiodec_switch"
86
87 enum {
88     /** No request*/
89     VCONFKEY_AUDIODEC_NONE = 0x0000,
90     /** Request for switching to software decoder */
91     VCONFKEY_AUDIODEC_TO_SWDEC = 0x0001,
92     /** Request for switching to hardware decoder */
93     VCONFKEY_AUDIODEC_TO_HWDEC = 0x0002,
94 };
95
96 static int role_is_dsp_playback(const pa_proplist *proplist)
97 {
98     const char *role;
99
100     if (NULL == proplist) {
101         pa_log_debug("[AUDIODSP]Sink input lacks property data.");
102         return 0;
103     }
104
105     /* Check whether it is a dsp sink-input */
106     role = pa_proplist_gets(proplist, PA_PROP_MEDIA_NAME);
107     if (NULL == role) {
108         pa_log_debug("[AUDIODSP]Sink input lacks property: %s", PA_PROP_MEDIA_NAME);
109         return 0;
110     }
111     if (!pa_streq(MEDIA_NAME_AUDIO_DSP, role)) {
112         pa_log_debug("[AUDIODSP]Sink input property: %s=%s, not \"DSP PLAYBACK\", skip it.", PA_PROP_MEDIA_NAME, role);
113         return 0;
114     }
115
116     return 1;
117 }
118
119 /* check if this sink is bluez */
120 static pa_bool_t is_bluez_sink (pa_sink* sink)
121 {
122     const char* api_name = NULL;
123
124     if (sink == NULL) {
125         pa_log_warn ("[AUDIODSP]input param sink is null");
126         return FALSE;
127     }
128
129     api_name = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_API);
130     if (api_name) {
131         if (pa_streq (api_name, BLUEZ_API)) {
132             return TRUE;
133         }
134     }
135
136     return FALSE;
137 }
138
139 static void sink_input_volume_changed (pa_sink_input *i)
140 {
141     if (change_volume) {
142         pa_volume_t avgvol = pa_cvolume_avg(&i->volume);
143         change_volume(avgvol);
144         pa_log_debug("[AUDIODSP]Successful in set volume=%u", avgvol);
145     } else {
146         pa_log_debug("[AUDIODSP]Missing plugin provided change_volume func");
147     }
148 }
149
150 static void sink_input_mute_changed(pa_sink_input *i)
151 {
152     if (set_mute) {
153         set_mute(i->muted);
154         pa_log_debug("[AUDIODSP]Set mute succeed.");
155     } else {
156         pa_log_debug("[AUDIODSP]Missing plugin provided set_mute func.");
157     }
158 }
159
160 static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u)
161 {
162     pa_sink *s = NULL;
163     int i=0;
164
165     if (0 == role_is_dsp_playback(new_data->proplist))
166         return PA_HOOK_OK;
167
168     PA_IDXSET_FOREACH(s, c->sinks, i) {
169         if (NULL != s->name && pa_streq(s->name, "null")) {
170             new_data->sink = s;
171             pa_log_debug("[AUDIODSP]set sink to null sink");
172             break;
173         }
174     }
175
176     return PA_HOOK_OK;
177 }
178
179 /* Called when new sink-input is creating */
180 static pa_hook_result_t sink_input_put_hook_callback(pa_core *core, pa_sink_input *i, struct userdata *u)
181 {
182     if (0 == role_is_dsp_playback(i->proplist))
183         return PA_HOOK_OK;
184
185     pa_log_debug("[AUDIODSP]Set volume_changed and mute_changed function");
186     i->volume_changed = sink_input_volume_changed;
187     i->mute_changed = sink_input_mute_changed;
188
189     return PA_HOOK_OK;
190 }
191
192 static pa_hook_result_t sink_input_put_hook_callback_rt_switch(pa_core *core, pa_sink_input *i, struct userdata *u)
193 {
194     pa_log_debug("[AUDIODSP]sink_input_put_hook_callback_rt_switch enter");
195     pa_sink *s = i->sink;
196
197     if (0 == role_is_dsp_playback(i->proplist)) {
198         pa_log_debug("[AUDIODSP]Not %s, skip.", MEDIA_NAME_AUDIO_DSP);
199         return PA_HOOK_OK;
200     }
201
202     if (is_bluez_sink(s)) {
203         pa_log_debug("[AUDIODSP]Find new bt sink input, set %s to VCONFKEY_AUDIODEC_TO_SWDEC:%d", VCONFKEY_AUDIODEC_SWITCH, VCONFKEY_AUDIODEC_TO_SWDEC);
204         vconf_set_int(VCONFKEY_AUDIODEC_SWITCH, VCONFKEY_AUDIODEC_TO_SWDEC);
205     }
206
207     pa_log_debug("[AUDIODSP]sink_input_put_hook_callback_rt_switch leave");
208     return PA_HOOK_OK;
209 }
210
211 static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
212 {
213     struct userdata *u = userdata;
214     pa_sink *def;
215     pa_assert(u);
216
217     pa_log_debug("[AUDIODSP][%s] subscribe_cb() t=[0x%x], idx=[%d]", __func__, t, idx);
218
219     /* We only handle server changes */
220     if (t == (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE)) {
221
222         def = pa_namereg_get_default_sink(c);
223         if (def == NULL) {
224             pa_log_warn("[AUDIODSP][%s] pa_namereg_get_default_sink() returns null", __func__);
225             return;
226         }
227
228         if (is_bluez_sink(def)) {
229             vconf_set_int(VCONFKEY_AUDIODEC_SWITCH, VCONFKEY_AUDIODEC_TO_SWDEC);
230         } else {
231             vconf_set_int(VCONFKEY_AUDIODEC_SWITCH, VCONFKEY_AUDIODEC_TO_HWDEC);
232         }
233     }
234 }
235
236 static int _audio_dsp_plugin_get_list(const char *plugdir ,char ***list)
237 {
238     struct dirent **entry = NULL;
239     int items;
240     struct stat finfo;
241     char **temp;
242     int tn = 0;
243     static char curdir[PATH_MAX];
244     int item_idx;
245     int ret = 0;
246
247     items = scandir(plugdir, &entry, NULL, alphasort);
248     pa_log_debug("[AUDIODSP]Items %d", items);
249
250     if (items == -1)
251         return -1;
252
253     temp = (char **)malloc(sizeof(char *) * (items + 1));
254     if(!temp) {
255         ret = -1;
256         goto free_entry;
257     }
258     memset(temp, 0, sizeof(char*) * (items + 1));
259     memset(curdir, '\0', sizeof(curdir));
260     if(NULL == getcwd(curdir, sizeof(curdir)-1)) {
261         if (temp) {
262             free (temp);
263             temp = NULL;
264         }
265         ret = -1;
266         goto free_entry;
267     }
268     /* FIXME : need to handle error case */
269     chdir(plugdir);
270
271     for(item_idx = items; item_idx--; ) {
272         if(stat(entry[item_idx]->d_name, &finfo) < 0) {
273             pa_log_error("[AUDIODSP]Stat error");
274             if (temp) {
275                 free(temp);
276                 temp = NULL;
277             }
278             ret = -1;
279             goto free_entry;
280         }
281
282         pa_log_debug("[AUDIODSP]item %d is %s", item_idx, entry[item_idx]->d_name);
283
284         if (S_ISREG(finfo.st_mode)) {
285             temp[tn] = __strcatdup(plugdir, "/");
286             temp[tn] = __strcatdup(temp[tn], entry[item_idx]->d_name);
287             tn++;
288         }
289     }
290     *list =  temp;
291 free_entry:
292     for(item_idx = 0; item_idx < items; item_idx++) {
293         free(entry[item_idx]);
294     }
295     free(entry);
296     return ret;
297 }
298
299 static void _audio_dsp_plugin_destroy_list(char **list)
300 {
301     int tn = 0;
302     while(list[tn]) {
303         free(list[tn++]);
304     }
305     free (list);
306 }
307
308 static int audio_dsp_plugin_scan(const char *plugindir)
309 {
310     char **list = NULL;
311     int err = 0;
312     char *item = NULL;
313     int index = 0;
314     int plugin_index = 0;
315
316     pa_log_debug("[AUDIODSP] Plugin dir :: %s", plugindir);
317     err = _audio_dsp_plugin_get_list(plugindir, &list);
318     if (err != 0)
319         return err;
320
321     while((item = list[index++]) != NULL) {
322         if(audio_dsp_plugin_open(item) != 0) {
323             pa_log_warn("[AUDIODSP]%s is not sound plugin", item);
324         } else {
325             break;
326         }
327     }
328
329     _audio_dsp_plugin_destroy_list(list);
330
331     return 0;
332 }
333
334 static int audio_dsp_plugin_open(char *file)
335 {
336     void (*func_change_vol) (pa_volume_t) = NULL;
337     void (*func_set_mute) (int) = NULL;
338
339     plugin = dlopen(file, RTLD_NOW|RTLD_GLOBAL);
340
341     if (NULL == plugin) {
342         pa_log_debug("[AUDIODSP]%s", dlerror());
343         return -1;
344     }
345
346     func_change_vol = (void (*)(pa_volume_t))dlsym(plugin, "change_dsp_volume");
347     if (NULL == func_change_vol) {
348         dlclose(plugin);
349         plugin = NULL;
350         pa_log_debug("[AUDIODSP]Cannot find symbol : change_dsp_volume");
351         return -1;
352     }
353
354     func_set_mute = (void (*)(int))dlsym(plugin, "set_mute");
355     if (NULL == func_set_mute) {
356         dlclose(plugin);
357         plugin = NULL;
358         pa_log_debug("[AUDIODSP]Cannot find symbol : set_mute");
359         return -1;
360     }
361
362     change_volume = func_change_vol;
363     set_mute = func_set_mute;
364
365     return 0;
366 }
367
368 static void audio_dsp_plugin_close()
369 {
370     if(plugin) {
371         dlclose(plugin);
372         plugin = NULL;
373     }
374 }
375
376 int pa__init(pa_module *m)
377 {
378     struct userdata *u;
379
380     pa_assert(m);
381     m->userdata = u = pa_xnew0(struct userdata, 1);
382     u->core = m->core;
383     u->module = m;
384     u->sink_input_new_hook_slot =
385             pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_hook_callback, u);
386     u->sink_input_put_hook_slot =
387             pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_put_hook_callback, u);
388     u->sink_input_put_hook_slot_rt_switch =
389             pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_hook_callback_rt_switch, u);
390
391     u->subscription = pa_subscription_new(u->core, PA_SUBSCRIPTION_MASK_SERVER, subscribe_cb, u);
392
393
394 #ifdef plugin_clvdir 
395
396     audio_dsp_plugin_scan(plugin_clvdir);
397 #else
398   #error "plugin_clvdir undefined"
399 #endif
400
401     pa_log_info("[AUDIODSP]audio dsp module is loaded\n");
402     return 0;
403 }
404
405 void pa__done(pa_module *m)
406 {
407     struct userdata* u;
408     pa_assert(m);
409     if (!(u = m->userdata))
410         return;
411     if (u->sink_input_new_hook_slot)
412         pa_hook_slot_free(u->sink_input_new_hook_slot);
413     if (u->sink_input_put_hook_slot)
414         pa_hook_slot_free(u->sink_input_put_hook_slot);
415     if (u->sink_input_put_hook_slot_rt_switch)
416         pa_hook_slot_free(u->sink_input_put_hook_slot_rt_switch);
417
418     if (u->subscription)
419         pa_subscription_free(u->subscription);
420
421     audio_dsp_plugin_close();
422
423     pa_xfree(u);
424 }
425
426 static char* __strcatdup(const char *str1, const char *str2)
427 {
428     char *dest = NULL;
429     int len = 0;
430     len = strlen(str1) + strlen(str2) + 1;
431     dest = (char*) malloc(len*sizeof(char));
432     if (!dest)
433         return NULL;
434     strncpy(dest, str1, len-1);
435     strncat(dest, str2, len-1);
436     return dest;
437 }