keysound: support keysound priority
[platform/core/multimedia/libmm-sound.git] / mm_sound_keysound.c
1 /*
2  * libmm-sound
3  *
4  * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved.
5  *
6  * Contact: Seungbae Shin <seungbae.shin@samsung.com>
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  */
21
22 #include <stdlib.h>
23 #include <memory.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <stdint.h>
27
28 #include <mm_error.h>
29 #include <mm_debug.h>
30 #include <mm_sound.h>
31 #include <glib.h>
32
33 #include "include/mm_sound_common.h"
34
35 #ifdef USE_LWIPC
36 #include <lwipc.h>
37 #define PULSEAUDIO_READY "/tmp/.pulseaudio_ready"
38 #endif
39
40 #define PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY     "SimplePlay"
41 #define PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP     "SimpleStop"
42 #define PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL "SimpleStopAll"
43
44 #define AUDIO_VOLUME_CONFIG_TYPE(vol) (vol & 0x00FF)
45 #define AUDIO_VOLUME_CONFIG_GAIN(vol) (vol & 0xFF00)
46
47 #define FILENAME_LEN 1024
48 #define MIN_KEYTONE_INTERVAL_US 30000
49 #define NO_PRIORITY (-1)
50
51 #define USE_PIPE /* default is pipe now */
52
53 #ifdef USE_PIPE
54
55 #define KEYTONE_PATH "/tmp/keytone"
56 #define ROLE_LEN 64
57 #define VOLUME_GAIN_TYPE_LEN 32
58 #define METHOD_LEN 32
59
60 #define MAX_WRITE_RETRY 50
61 #define WRITE_RETRY_INTERVAL_MS 20
62
63 #define MM_SOUND_SIMPLE __mm_sound_simple_pipe
64
65 typedef struct ipc_data {
66         char filename[FILENAME_LEN];
67         char role[ROLE_LEN];
68         char volume_gain_type[VOLUME_GAIN_TYPE_LEN];
69         char method[METHOD_LEN];
70         int priority; /* lower as a higher priority, -1 for no priority (= don't care) */
71 } ipc_t;
72
73 static int __mm_sound_simple_pipe(const char *filename, int volume_config, int priority, const char *method);
74
75 #else /* USE_PIPE */
76
77 #include <gio/gio.h>
78
79 #define PA_BUS_NAME                                 "org.pulseaudio.Server"
80 #define PA_SOUND_PLAYER_OBJECT_PATH                 "/org/pulseaudio/SoundPlayer"
81 #define PA_SOUND_PLAYER_INTERFACE                   "org.pulseaudio.SoundPlayer"
82 #define PA_SOUND_RESPONSE_TIMEOUT                   5000
83
84 #define MM_SOUND_SIMPLE __mm_sound_simple_dbus
85
86 static int __mm_sound_simple_dbus(const char *filename, int volume_config, int priority, const char *method);
87 #endif /* USE_PIPE */
88
89 static const char* __convert_volume_type_to_role(int volume_type)
90 {
91         debug_log("volume_type(%d)", volume_type);
92         switch (volume_type) {
93         case VOLUME_TYPE_MEDIA:
94                 return "media";
95         case VOLUME_TYPE_SYSTEM:
96                 return "system";
97         case VOLUME_TYPE_NOTIFICATION:
98                 return "notification";
99         case VOLUME_TYPE_ALARM:
100                 return "alarm";
101         case VOLUME_TYPE_VOICE:
102                 return "voice";
103         case VOLUME_TYPE_RINGTONE:
104                 return "ringtone-call";
105         default:
106                 debug_warning("not supported type(%d), we change it SYSTEM type forcibly", volume_type);
107                 return "system";
108         }
109 }
110
111 static const char* __convert_volume_gain_type_to_string(int volume_gain_type)
112 {
113         debug_log("volume_gain_type(0x%x)", volume_gain_type);
114         switch (volume_gain_type) {
115         case VOLUME_GAIN_DEFAULT:
116                 return "";
117         case VOLUME_GAIN_DIALER:
118                 return "dialer";
119         case VOLUME_GAIN_TOUCH:
120                 return "touch";
121         case VOLUME_GAIN_AF:
122                 return "af";
123         case VOLUME_GAIN_SHUTTER1:
124                 return "shutter1";
125         case VOLUME_GAIN_SHUTTER2:
126                 return "shutter2";
127         case VOLUME_GAIN_CAMCORDING:
128                 return "camcording";
129         case VOLUME_GAIN_MIDI:
130                 return "midi";
131         case VOLUME_GAIN_BOOTING:
132                 return "booting";
133         case VOLUME_GAIN_VIDEO:
134                 return "video";
135         case VOLUME_GAIN_TTS:
136                 return "tts";
137         default:
138                 return "";
139         }
140 }
141
142 static uint64_t __elapsed_time_us(struct timespec *start, struct timespec *end)
143 {
144         uint64_t start_us = (uint64_t) start->tv_sec * 1000000 +
145                                                 (uint64_t) start->tv_nsec / 1000;
146         uint64_t end_us = (uint64_t) end->tv_sec * 1000000 +
147                                           (uint64_t) end->tv_nsec / 1000;
148
149         return end_us - start_us;
150 }
151
152 static bool __is_interval_too_short(const char *filename)
153 {
154         static struct timespec ts_last_played = { .tv_sec = 0, .tv_nsec = 0 };
155         static char file_last_played[FILENAME_LEN] = { '\0', };
156
157         struct timespec ts_now;
158         clock_gettime(CLOCK_MONOTONIC, &ts_now);
159
160         if (g_strcmp0(file_last_played, filename) == 0 &&
161                 __elapsed_time_us(&ts_last_played, &ts_now) < MIN_KEYTONE_INTERVAL_US) {
162                 return true;
163         }
164
165         g_strlcpy(file_last_played, filename, sizeof(file_last_played));
166         ts_last_played = ts_now;
167
168         return false;
169 }
170
171 EXPORT_API
172 int mm_sound_play_keysound_priority(const char *filename, int volume_config, int priority)
173 {
174 #ifndef TIZEN_TV
175         if (__is_interval_too_short(filename)) {
176                 debug_warning("Too short time (< %u us) to play same file, try next time again...", MIN_KEYTONE_INTERVAL_US);
177                 return MM_ERROR_NONE;
178         }
179 #endif
180
181         return MM_SOUND_SIMPLE(filename, volume_config, priority, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY);
182 }
183
184 EXPORT_API
185 int mm_sound_play_keysound(const char *filename, int volume_config)
186 {
187         return mm_sound_play_keysound_priority(filename, volume_config, NO_PRIORITY);
188 }
189
190 EXPORT_API
191 int mm_sound_stop_keysound(const char *filename)
192 {
193         return MM_SOUND_SIMPLE(filename, 0, NO_PRIORITY,
194                                                 filename ? PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP : PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL);
195 }
196
197 #ifdef USE_LWIPC
198 static bool _mm_sound_check_pa_ready()
199 {
200         int ret = 0;
201         static bool is_pa_ready = false;
202
203         if (is_pa_ready)
204                 return true;
205
206         debug_msg("LwipcIsDone start >> ");
207         ret = LwipcIsDone(PULSEAUDIO_READY);
208         debug_msg("LwipcIsDone end << %d", ret);
209
210         is_pa_ready = (ret > 0) ? true : false;
211
212         return is_pa_ready;
213 }
214 #endif
215
216 static int __verify_input_file(const char *filename)
217 {
218         if (!filename)
219                 return MM_ERROR_SOUND_INVALID_FILE;
220
221         /* Check whether file exists */
222         if (access(filename, F_OK) == -1) {
223                 char str_error[256];
224
225                 strerror_r(errno, str_error, sizeof(str_error));
226                 debug_error("file [%s] doesn't exists : [%s][%d]", filename, str_error, errno);
227
228                 return MM_ERROR_SOUND_FILE_NOT_FOUND;
229         }
230
231         return MM_ERROR_NONE;
232 }
233
234 #ifdef USE_PIPE
235 static int __mm_sound_simple_pipe(const char *filename, int volume_config, int priority, const char *method)
236 {
237         int ret = MM_ERROR_NONE;
238         int retry_remaining = MAX_WRITE_RETRY;
239         size_t size_to_write = sizeof(ipc_t);
240         ssize_t written = 0;
241         int fd = -1;
242         ipc_t data = { 0, };
243
244 #ifdef USE_LWIPC
245         if (!_mm_sound_check_pa_ready()) {
246                 debug_error("Pulseaudio is not ready!");
247                 return MM_ERROR_SOUND_INVALID_STATE;
248         }
249 #endif
250
251         if (!method)
252                 return MM_ERROR_INVALID_ARGUMENT;
253
254         /* Prepare data to be send */
255         g_strlcpy(data.method, method, METHOD_LEN);
256
257         if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL) != 0) {
258                 ret = __verify_input_file(filename);
259                 if (ret != MM_ERROR_NONE)
260                         return ret;
261
262                 g_strlcpy(data.filename, filename, FILENAME_LEN);
263         }
264
265         if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY) == 0) {
266                 g_strlcpy(data.role,
267                                 __convert_volume_type_to_role(AUDIO_VOLUME_CONFIG_TYPE(volume_config)),
268                                 ROLE_LEN);
269                 g_strlcpy(data.volume_gain_type,
270                                 __convert_volume_gain_type_to_string(AUDIO_VOLUME_CONFIG_GAIN(volume_config)),
271                                 VOLUME_GAIN_TYPE_LEN);
272                 data.priority = priority;
273         }
274
275         debug_msg("filepath=[%s], role=[%s], volume_gain_type=[%s], priority=[%d], method=[%s]",
276                         data.filename, data.role, data.volume_gain_type, data.priority, data.method);
277
278         /* Open PIPE */
279         fd = open(KEYTONE_PATH, O_WRONLY | O_NONBLOCK);
280         if (fd == -1) {
281                 char str_error[256];
282
283                 strerror_r(errno, str_error, sizeof(str_error));
284                 debug_error("Fail to open pipe: [%s][%d]", str_error, errno);
285
286                 return MM_ERROR_SOUND_INTERNAL;
287         }
288
289         /* Write to PIPE */
290         do {
291                 written = write(fd, &data, size_to_write);
292                 if (written == -1) {
293                         char str_error[256];
294                         int errsv = errno;
295
296                         strerror_r(errsv, str_error, sizeof(str_error));
297                         debug_error("[%d] Fail to write, written = %zd, [%s][%d]",
298                                                 MAX_WRITE_RETRY - retry_remaining, written, str_error, errsv);
299
300                         if (errsv != EAGAIN || --retry_remaining == 0) {
301                                 ret = MM_ERROR_SOUND_INTERNAL;
302                                 break;
303                         }
304
305                         usleep(WRITE_RETRY_INTERVAL_MS * 1000);
306                 } else if (retry_remaining != MAX_WRITE_RETRY) {
307                         debug_msg("retry success!!");
308                 }
309         } while (written != size_to_write);
310
311         /* Close PIPE */
312         close(fd);
313
314         return ret;
315 }
316
317 #else /* USE_PIPE */
318 static int __mm_sound_simple_dbus(const char *filename, int volume_config, int priority, const char *method)
319 {
320         int ret = MM_ERROR_NONE;
321         const char *role = NULL;
322         const char *vol_gain_type = NULL;
323         GVariant *result = NULL;
324         GDBusConnection *conn = NULL;
325         GError *err = NULL;
326         int idx = 0;
327
328 #ifdef USE_LWIPC
329         if (!_mm_sound_check_pa_ready()) {
330                 debug_error("Pulseaudio is not ready!");
331                 return MM_ERROR_SOUND_INVALID_STATE;
332         }
333 #endif
334         if (!method)
335                 return MM_ERROR_INVALID_ARGUMENT;
336
337         conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &err);
338         if (!conn || err) {
339                 debug_error("g_bus_get_sync() error (%s)", err ? err->message : NULL);
340                 g_error_free(err);
341                 return MM_ERROR_SOUND_INTERNAL;
342         }
343
344         if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY) == 0) {
345                 ret = __verify_input_file(filename);
346                 if (ret != MM_ERROR_NONE)
347                         goto END;
348
349                 role = __convert_volume_type_to_role(AUDIO_VOLUME_CONFIG_TYPE(volume_config));
350                 vol_gain_type = __convert_volume_gain_type_to_string(AUDIO_VOLUME_CONFIG_GAIN(volume_config));
351
352                 result = g_dbus_connection_call_sync(conn, PA_BUS_NAME,
353                                                                                         PA_SOUND_PLAYER_OBJECT_PATH,
354                                                                                         PA_SOUND_PLAYER_INTERFACE,
355                                                                                         PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY,
356                                                                                         g_variant_new("(sssi)", filename, role, vol_gain_type, priority),
357                                                                                         NULL, G_DBUS_CALL_FLAGS_NONE, PA_SOUND_RESPONSE_TIMEOUT, NULL, &err);
358                 if (!result || err) {
359                         debug_error("g_dbus_connection_call_sync() for %s error (%s)", method, err ? err->message : NULL);
360                         ret = MM_ERROR_SOUND_INTERNAL;
361                         goto END;
362                 }
363
364                 g_variant_get(result, "(i)", &idx);
365                 if (idx == -1) {
366                         debug_error("[%s] failure, filename(%s)/role(%s)/gain(%s)/priority(%d)/stream idx(%d)",
367                                         method, filename, role, vol_gain_type, priority, idx);
368                         ret = MM_ERROR_SOUND_INTERNAL;
369                         goto END;
370                 }
371                 debug_msg("[%s] success, filename(%s)/role(%s)/gain(%s)/priority(%d),stream idx(%d)",
372                                 method, filename, role, vol_gain_type, priority, idx);
373
374         } else if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP) == 0) {
375                 ret = __verify_input_file(filename);
376                 if (ret != MM_ERROR_NONE)
377                         goto END;
378
379                 result = g_dbus_connection_call_sync(conn, PA_BUS_NAME,
380                                                                                         PA_SOUND_PLAYER_OBJECT_PATH,
381                                                                                         PA_SOUND_PLAYER_INTERFACE,
382                                                                                         PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP,
383                                                                                         g_variant_new("(s)", filename),
384                                                                                         NULL, G_DBUS_CALL_FLAGS_NONE, PA_SOUND_RESPONSE_TIMEOUT, NULL, &err);
385                 if (!result || err) {
386                         debug_error("g_dbus_connection_call_sync() for %s error (%s)", method, err ? err->message : NULL);
387                         ret = MM_ERROR_SOUND_INTERNAL;
388                         goto END;
389                 }
390                 debug_msg("[%s] done, filename(%s)", method, filename);
391
392         } else if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL) == 0) {
393                 result = g_dbus_connection_call_sync(conn, PA_BUS_NAME,
394                                                                                         PA_SOUND_PLAYER_OBJECT_PATH,
395                                                                                         PA_SOUND_PLAYER_INTERFACE,
396                                                                                         PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL,
397                                                                                         NULL,
398                                                                                         NULL, G_DBUS_CALL_FLAGS_NONE, PA_SOUND_RESPONSE_TIMEOUT, NULL, &err);
399                 if (!result || err) {
400                         debug_error("g_dbus_connection_call_sync() for %s error (%s)", method, err ? err->message : NULL);
401                         ret = MM_ERROR_SOUND_INTERNAL;
402                         goto END;
403                 }
404                 debug_msg("[%s] done", method);
405
406         } else {
407                 debug_error("Invalid Method : %s", method);
408                 ret = MM_ERROR_INVALID_ARGUMENT;
409         }
410
411 END:
412         if (err)
413                 g_error_free(err);
414         if (result)
415                 g_variant_unref(result);
416         if (conn)
417                 g_object_unref(conn);
418
419         return ret;
420 }
421 #endif /* USE_PIPE */