keysound: skip hard frequent request of keysound playback
[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 USE_PIPE /* default is pipe now */
48
49 #ifdef USE_PIPE
50
51 #define KEYTONE_PATH "/tmp/keytone"
52 #define FILENAME_LEN 1024
53 #define ROLE_LEN 64
54 #define VOLUME_GAIN_TYPE_LEN 32
55 #define METHOD_LEN 32
56
57 #define MAX_WRITE_RETRY 50
58 #define WRITE_RETRY_INTERVAL_MS 20
59
60 #define MIN_KEYTONE_INTERVAL_US 30000
61
62 typedef struct ipc_data {
63         char filename[FILENAME_LEN];
64         char role[ROLE_LEN];
65         char volume_gain_type[VOLUME_GAIN_TYPE_LEN];
66         char method[METHOD_LEN];
67 } ipc_t;
68
69 static int __mm_sound_simple_pipe(const char *filename, int volume_config, const char *method);
70 #else /* USE_PIPE */
71 #include <gio/gio.h>
72
73 #define PA_BUS_NAME                                 "org.pulseaudio.Server"
74 #define PA_SOUND_PLAYER_OBJECT_PATH                 "/org/pulseaudio/SoundPlayer"
75 #define PA_SOUND_PLAYER_INTERFACE                   "org.pulseaudio.SoundPlayer"
76 #define PA_SOUND_RESPONSE_TIMEOUT                   2000
77
78 static int __mm_sound_simple_dbus(const char *filename, int volume_config, const char *method);
79 #endif /* USE_PIPE */
80
81 static const char* __convert_volume_type_to_role(int volume_type)
82 {
83         debug_log("volume_type(%d)", volume_type);
84         switch (volume_type) {
85         case VOLUME_TYPE_MEDIA:
86                 return "media";
87         case VOLUME_TYPE_SYSTEM:
88                 return "system";
89         case VOLUME_TYPE_NOTIFICATION:
90                 return "notification";
91         case VOLUME_TYPE_ALARM:
92                 return "alarm";
93         case VOLUME_TYPE_VOICE:
94                 return "voice";
95         case VOLUME_TYPE_RINGTONE:
96                 return "ringtone-call";
97         default:
98                 debug_warning("not supported type(%d), we change it SYSTEM type forcibly", volume_type);
99                 return "system";
100         }
101 }
102
103 static const char* __convert_volume_gain_type_to_string(int volume_gain_type)
104 {
105         debug_log("volume_gain_type(0x%x)", volume_gain_type);
106         switch (volume_gain_type) {
107         case VOLUME_GAIN_DEFAULT:
108                 return "";
109         case VOLUME_GAIN_DIALER:
110                 return "dialer";
111         case VOLUME_GAIN_TOUCH:
112                 return "touch";
113         case VOLUME_GAIN_AF:
114                 return "af";
115         case VOLUME_GAIN_SHUTTER1:
116                 return "shutter1";
117         case VOLUME_GAIN_SHUTTER2:
118                 return "shutter2";
119         case VOLUME_GAIN_CAMCORDING:
120                 return "camcording";
121         case VOLUME_GAIN_MIDI:
122                 return "midi";
123         case VOLUME_GAIN_BOOTING:
124                 return "booting";
125         case VOLUME_GAIN_VIDEO:
126                 return "video";
127         case VOLUME_GAIN_TTS:
128                 return "tts";
129         default:
130                 return "";
131         }
132 }
133
134 static uint64_t __elapsed_time_us(struct timespec *start, struct timespec *end)
135 {
136         uint64_t start_us = (uint64_t) start->tv_sec * 1000000 +
137                                                 (uint64_t) start->tv_nsec / 1000;
138         uint64_t end_us = (uint64_t) end->tv_sec * 1000000 +
139                                           (uint64_t) end->tv_nsec / 1000;
140
141         return end_us - start_us;
142 }
143
144 static bool __is_interval_too_short(const char *filename)
145 {
146         static struct timespec ts_last_played = { .tv_sec = 0, .tv_nsec = 0 };
147         static char file_last_played[FILENAME_LEN] = { '\0', };
148
149         struct timespec ts_now;
150         clock_gettime(CLOCK_MONOTONIC, &ts_now);
151
152         if (g_strcmp0(file_last_played, filename) == 0 &&
153                 __elapsed_time_us(&ts_last_played, &ts_now) < MIN_KEYTONE_INTERVAL_US) {
154                 return true;
155         }
156
157         g_strlcpy(file_last_played, filename, sizeof(file_last_played));
158         ts_last_played = ts_now;
159
160         return false;
161 }
162
163 EXPORT_API
164 int mm_sound_play_keysound(const char *filename, int volume_config)
165 {
166 #ifndef TIZEN_TV
167         if (__is_interval_too_short(filename)) {
168                 debug_warning("Too short time (< %u us) to play same file, try next time again...", MIN_KEYTONE_INTERVAL_US);
169                 return MM_ERROR_NONE;
170         }
171 #endif
172
173 #ifdef USE_PIPE
174         return __mm_sound_simple_pipe(filename, volume_config, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY);
175 #else
176         return __mm_sound_simple_dbus(filename, volume_config, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY);
177 #endif
178 }
179
180 EXPORT_API
181 int mm_sound_stop_keysound(const char *filename)
182 {
183 #ifdef USE_PIPE
184         return __mm_sound_simple_pipe(filename, 0,
185                         (filename) ? PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP : PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL);
186 #else
187         return __mm_sound_simple_dbus(filename, 0,
188                         (filename) ? PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP : PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL);
189 #endif
190 }
191
192 #ifdef USE_LWIPC
193 static bool _mm_sound_check_pa_ready()
194 {
195         int ret = 0;
196         static bool is_pa_ready = false;
197
198         if (is_pa_ready)
199                 return true;
200
201         debug_msg("LwipcIsDone start >> ");
202         ret = LwipcIsDone(PULSEAUDIO_READY);
203         debug_msg("LwipcIsDone end << %d", ret);
204
205         is_pa_ready = (ret > 0) ? true : false;
206
207         return is_pa_ready;
208 }
209 #endif
210
211 static int __verify_input_file(const char *filename)
212 {
213         if (!filename)
214                 return MM_ERROR_SOUND_INVALID_FILE;
215
216         /* Check whether file exists */
217         if (access(filename, F_OK) == -1) {
218                 char str_error[256];
219
220                 strerror_r(errno, str_error, sizeof(str_error));
221                 debug_error("file [%s] doesn't exists : [%s][%d]", filename, str_error, errno);
222
223                 return MM_ERROR_SOUND_FILE_NOT_FOUND;
224         }
225
226         return MM_ERROR_NONE;
227 }
228
229 #ifdef USE_PIPE
230 static int __mm_sound_simple_pipe(const char *filename, int volume_config, const char *method)
231 {
232         int ret = MM_ERROR_NONE;
233         int retry_remaining = MAX_WRITE_RETRY;
234         size_t size_to_write = sizeof(ipc_t);
235         ssize_t written = 0;
236         int fd = -1;
237         ipc_t data = { 0, };
238
239 #ifdef USE_LWIPC
240         if (!_mm_sound_check_pa_ready()) {
241                 debug_error("Pulseaudio is not ready!");
242                 return MM_ERROR_SOUND_INVALID_STATE;
243         }
244 #endif
245
246         if (!method)
247                 return MM_ERROR_INVALID_ARGUMENT;
248
249         /* Prepare data to be send */
250         g_strlcpy(data.method, method, METHOD_LEN);
251
252         if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL) != 0) {
253                 ret = __verify_input_file(filename);
254                 if (ret != MM_ERROR_NONE)
255                         return ret;
256
257                 g_strlcpy(data.filename, filename, FILENAME_LEN);
258         }
259
260         if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY) == 0) {
261                 g_strlcpy(data.role,
262                                 __convert_volume_type_to_role(AUDIO_VOLUME_CONFIG_TYPE(volume_config)),
263                                 ROLE_LEN);
264                 g_strlcpy(data.volume_gain_type,
265                                 __convert_volume_gain_type_to_string(AUDIO_VOLUME_CONFIG_GAIN(volume_config)),
266                                 VOLUME_GAIN_TYPE_LEN);
267         }
268
269         debug_msg("filepath=[%s], role=[%s], volume_gain_type=[%s], method=[%s]",
270                         data.filename, data.role, data.volume_gain_type, data.method);
271
272         /* Open PIPE */
273         fd = open(KEYTONE_PATH, O_WRONLY | O_NONBLOCK);
274         if (fd == -1) {
275                 char str_error[256];
276
277                 strerror_r(errno, str_error, sizeof(str_error));
278                 debug_error("Fail to open pipe: [%s][%d]", str_error, errno);
279
280                 return MM_ERROR_SOUND_INTERNAL;
281         }
282
283         /* Write to PIPE */
284         do {
285                 written = write(fd, &data, size_to_write);
286                 if (written == -1) {
287                         char str_error[256];
288                         int errsv = errno;
289
290                         strerror_r(errsv, str_error, sizeof(str_error));
291                         debug_error("[%d] Fail to write, written = %zd, [%s][%d]",
292                                                 MAX_WRITE_RETRY - retry_remaining, written, str_error, errsv);
293
294                         if (errsv != EAGAIN || --retry_remaining == 0) {
295                                 ret = MM_ERROR_SOUND_INTERNAL;
296                                 break;
297                         }
298
299                         usleep(WRITE_RETRY_INTERVAL_MS * 1000);
300                 } else if (retry_remaining != MAX_WRITE_RETRY) {
301                         debug_msg("retry success!!");
302                 }
303         } while (written != size_to_write);
304
305         /* Close PIPE */
306         close(fd);
307
308         return ret;
309 }
310
311 #else /* USE_PIPE */
312 static int __mm_sound_simple_dbus(const char *filename, int volume_config, const char *method)
313 {
314         int ret = MM_ERROR_NONE;
315         const char *role = NULL;
316         const char *vol_gain_type = NULL;
317         GVariant *result = NULL;
318         GDBusConnection *conn = NULL;
319         GError *err = NULL;
320         int idx = 0;
321
322 #ifdef USE_LWIPC
323         if (!_mm_sound_check_pa_ready()) {
324                 debug_error("Pulseaudio is not ready!");
325                 return MM_ERROR_SOUND_INVALID_STATE;
326         }
327 #endif
328         if (!method)
329                 return MM_ERROR_INVALID_ARGUMENT;
330
331         conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &err);
332         if (!conn || err) {
333                 debug_error("g_bus_get_sync() error (%s)", err ? err->message : NULL);
334                 g_error_free(err);
335                 return MM_ERROR_SOUND_INTERNAL;
336         }
337
338         if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY) == 0) {
339                 ret = __verify_input_file(filename);
340                 if (ret != MM_ERROR_NONE)
341                         goto END;
342
343                 role = __convert_volume_type_to_role(AUDIO_VOLUME_CONFIG_TYPE(volume_config));
344                 vol_gain_type = __convert_volume_gain_type_to_string(AUDIO_VOLUME_CONFIG_GAIN(volume_config));
345
346                 result = g_dbus_connection_call_sync(conn, PA_BUS_NAME,
347                                                                                         PA_SOUND_PLAYER_OBJECT_PATH,
348                                                                                         PA_SOUND_PLAYER_INTERFACE,
349                                                                                         PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY,
350                                                                                         g_variant_new("(sss)", filename, role, vol_gain_type),
351                                                                                         NULL, G_DBUS_CALL_FLAGS_NONE, PA_SOUND_RESPONSE_TIMEOUT, NULL, &err);
352                 if (!result || err) {
353                         debug_error("g_dbus_connection_call_sync() for %s error (%s)", method, err ? err->message : NULL);
354                         ret = MM_ERROR_SOUND_INTERNAL;
355                         goto END;
356                 }
357
358                 g_variant_get(result, "(i)", &idx);
359                 if (idx == -1) {
360                         debug_error("[%s] failure, filename(%s)/role(%s)/gain(%s)/stream idx(%d)",
361                                         method, filename, role, vol_gain_type, idx);
362                         ret = MM_ERROR_SOUND_INTERNAL;
363                         goto END;
364                 }
365                 debug_msg("[%s] success, filename(%s)/role(%s)/gain(%s)/stream idx(%d)",
366                                 method, filename, role, vol_gain_type, idx);
367
368         } else if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP) == 0) {
369                 ret = __verify_input_file(filename);
370                 if (ret != MM_ERROR_NONE)
371                         goto END;
372
373                 result = g_dbus_connection_call_sync(conn, PA_BUS_NAME,
374                                                                                         PA_SOUND_PLAYER_OBJECT_PATH,
375                                                                                         PA_SOUND_PLAYER_INTERFACE,
376                                                                                         PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP,
377                                                                                         g_variant_new("(s)", filename),
378                                                                                         NULL, G_DBUS_CALL_FLAGS_NONE, PA_SOUND_RESPONSE_TIMEOUT, NULL, &err);
379                 if (!result || err) {
380                         debug_error("g_dbus_connection_call_sync() for %s error (%s)", method, err ? err->message : NULL);
381                         ret = MM_ERROR_SOUND_INTERNAL;
382                         goto END;
383                 }
384                 debug_msg("[%s] done, filename(%s)", method, filename);
385
386         } else if (g_strcmp0(method, PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL) == 0) {
387                 result = g_dbus_connection_call_sync(conn, PA_BUS_NAME,
388                                                                                         PA_SOUND_PLAYER_OBJECT_PATH,
389                                                                                         PA_SOUND_PLAYER_INTERFACE,
390                                                                                         PA_SOUND_PLAYER_METHOD_NAME_SIMPLE_STOP_ALL,
391                                                                                         NULL,
392                                                                                         NULL, G_DBUS_CALL_FLAGS_NONE, PA_SOUND_RESPONSE_TIMEOUT, NULL, &err);
393                 if (!result || err) {
394                         debug_error("g_dbus_connection_call_sync() for %s error (%s)", method, err ? err->message : NULL);
395                         ret = MM_ERROR_SOUND_INTERNAL;
396                         goto END;
397                 }
398                 debug_msg("[%s] done", method);
399
400         } else {
401                 debug_error("Invalid Method : %s", method);
402                 ret = MM_ERROR_INVALID_ARGUMENT;
403         }
404
405 END:
406         if (err)
407                 g_error_free(err);
408         if (result)
409                 g_variant_unref(result);
410         if (conn)
411                 g_object_unref(conn);
412
413         return ret;
414 }
415 #endif /* USE_PIPE */