[Edje] Implement thread synchronisation feature between main thread and player thread...
[framework/uifw/edje.git] / src / lib / edje_multisense.c
1 #include "edje_private.h"
2
3 typedef struct _Multisense_Data
4 {
5    Edje_Multisense_Env *msenv;
6 #ifdef HAVE_LIBREMIX
7    RemixDeck *deck;
8    RemixTrack *track;
9    RemixLayer *snd_layer, *player_layer;
10    RemixBase *player;
11    RemixBase *player_snd;
12    int remaining;
13    int offset;
14    Eina_List *snd_src_list;
15
16    MULTISENSE_SOUND_PLAYER_GET_FUNC multisense_sound_player_get;
17 #endif
18 }Multisense_Data;
19
20 #define BUF_LEN 64
21 #define SND_PROCESS_LENGTH 2048
22 #define TIMEOUT_FOR_MM_HANDLER 10
23
24 #ifdef ENABLE_MULTISENSE
25 static Ecore_Thread *player_thread = NULL;
26 static int command_pipe[2];
27 static Eina_Bool pipe_initialized = EINA_FALSE;
28 static Ecore_Timer *idletimer = NULL;
29 static Eina_Condition eina_player_cond;
30 static Eina_Lock eina_player_mutex;
31 #endif
32
33 typedef enum _Edje_Sound_Action_Type
34 {
35    EDJE_PLAY_SAMPLE = 0,
36    EDJE_PLAY_TONE,
37    /*
38    EDJE_PLAY_PATTERN,
39    EDJE_PLAY_INSTRUMENT,
40    EDJE_PLAY_SONG,
41    */
42    EDJE_CLOSE_HANDLE,
43    EDJE_SOUND_LAST
44 } Edje_Sound_Action_Type;
45
46 typedef struct _Edje_Sample_Action Edje_Sample_Action;
47 typedef struct _Edje_Tone_Action Edje_Tone_Action;
48 typedef struct _Edje_Multisense_Sound_Action Edje_Multisense_Sound_Action;
49
50 struct _Edje_Sample_Action
51 {
52    char sample_name[BUF_LEN];
53    double speed;
54 };
55
56 struct _Edje_Tone_Action
57 {
58    char tone_name[BUF_LEN];
59    double duration;
60 };
61
62 struct _Edje_Multisense_Sound_Action
63 {
64    Edje *ed;
65    Edje_Sound_Action_Type action;
66    union {
67       Edje_Sample_Action sample;
68       Edje_Tone_Action tone;
69    } type;
70    Edje_File *snd_file;
71 };
72 #ifdef ENABLE_MULTISENSE
73 static Eina_Module *m = NULL;
74 static Eina_Bool _edje_multisense_Timer_Callback(void *data);
75
76 static Eina_Bool
77 edje_multisense_create_timer(void)
78 {
79    ecore_thread_main_loop_begin();
80    if (idletimer) ecore_timer_del(idletimer);
81    idletimer = ecore_timer_add(TIMEOUT_FOR_MM_HANDLER,
82                                   _edje_multisense_Timer_Callback, NULL);
83
84    ecore_thread_main_loop_end();
85    return EINA_TRUE;
86 }
87
88 static Eina_Bool
89 edje_multisense_kill_timer(void)
90 {
91    ecore_thread_main_loop_begin();
92    if (idletimer) ecore_timer_del(idletimer);
93    idletimer = NULL;
94    ecore_thread_main_loop_end();
95    return EINA_TRUE;
96 }
97
98 static Eina_Bool
99 _edje_multisense_Timer_Callback(void *data)
100 {
101 #if defined(ENABLE_MULTISENSE) && defined(HAVE_LIBREMIX)
102    ssize_t size = 0;
103    Edje_Multisense_Sound_Action command = {0,};
104
105    if ((!pipe_initialized) && (!player_thread)) return ECORE_CALLBACK_CANCEL;
106
107    //post a command to handle mm sound cleanup
108    command.action = EDJE_CLOSE_HANDLE;
109    size = write(command_pipe[1], &command, sizeof(command));
110    idletimer = NULL;
111 #endif
112    return ECORE_CALLBACK_CANCEL;
113 }
114
115 static Multisense_Data *
116 init_multisense_environment(void)
117 {
118    Multisense_Data *msdata;
119    char ms_factory[BUF_LEN];
120    char *ms_factory_env;
121    MULTISENSE_FACTORY_INIT_FUNC multisense_factory_init;
122
123    msdata = calloc(1, sizeof(Multisense_Data));
124    if (!msdata) goto err;
125
126    msdata->msenv = calloc(1, sizeof(Edje_Multisense_Env));
127    if (!msdata->msenv) goto err;
128
129    ms_factory_env = getenv("MULTISENSE_FACTORY");
130    if (ms_factory_env)
131      strncpy(ms_factory, ms_factory_env, BUF_LEN);
132    else
133      strcpy(ms_factory, "multisense_factory");
134
135    m = _edje_module_handle_load(ms_factory);
136    if (!m) goto err;
137 #ifdef HAVE_LIBREMIX
138    msdata->msenv->remixenv = remix_init();
139 #endif
140    multisense_factory_init = 
141      eina_module_symbol_get(m, "multisense_factory_init");
142    if (multisense_factory_init) multisense_factory_init(msdata->msenv);
143 #ifdef HAVE_LIBREMIX
144    msdata->multisense_sound_player_get = 
145      eina_module_symbol_get(m, "multisense_sound_player_get");
146    if (!msdata->multisense_sound_player_get) goto err;
147
148    msdata->deck = remix_deck_new(msdata->msenv->remixenv);
149    msdata->track = remix_track_new(msdata->msenv->remixenv, msdata->deck);
150    msdata->snd_layer = remix_layer_new_ontop(msdata->msenv->remixenv,
151                                              msdata->track,
152                                              REMIX_TIME_SAMPLES);
153    msdata->player_layer = remix_layer_new_ontop(msdata->msenv->remixenv,
154                                                 msdata->track,
155                                                 REMIX_TIME_SAMPLES);
156    msdata->player = msdata->multisense_sound_player_get(msdata->msenv);
157    if (!msdata->player) goto err;
158    msdata->player_snd = remix_sound_new(msdata->msenv->remixenv,
159                                         msdata->player, msdata->player_layer,
160                                         REMIX_SAMPLES(0),
161                                         REMIX_SAMPLES(REMIX_COUNT_INFINITE));
162 #endif
163    return msdata;
164
165 err:
166    if (msdata)
167      {
168 #ifdef HAVE_LIBREMIX
169         if (msdata->deck) remix_destroy(msdata->msenv->remixenv, msdata->deck);
170         if (msdata->msenv->remixenv) remix_purge(msdata->msenv->remixenv);
171 #endif
172         if (msdata->msenv) free(msdata->msenv);
173         free(msdata);
174      }
175    return NULL;
176 }
177 #endif
178
179 #if defined(ENABLE_MULTISENSE) && defined(HAVE_LIBREMIX)
180 static RemixBase *
181 eet_sound_reader_get(Edje_Multisense_Env *msenv, const char *path,
182                      const char *sound_id, const double speed)
183 {
184    RemixPlugin *sf_plugin = NULL;
185    RemixBase * eet_snd_reader = NULL;
186    int sf_path_key = 0;
187    int sf_sound_id_key = 0;
188    int sf_speed_key = 0;
189    CDSet *sf_parms = NULL;
190    RemixEnv *env = msenv->remixenv;
191
192    if (sf_plugin == NULL)
193      {
194         sf_plugin = remix_find_plugin(env, "eet_sndfile_reader");
195         if (sf_plugin == NULL)
196           {
197              ERR ("Multisense EET Sound reader plugin NULL\n");
198              return NULL;
199           }
200
201         sf_path_key = remix_get_init_parameter_key(env, sf_plugin, "path");
202         sf_sound_id_key = remix_get_init_parameter_key(env, sf_plugin, "sound_id");
203         sf_speed_key = remix_get_init_parameter_key(env, sf_plugin, "speed");
204      }
205    sf_parms = cd_set_replace(env, sf_parms, sf_path_key, CD_STRING(path));
206    sf_parms = cd_set_replace(env, sf_parms, sf_sound_id_key, CD_STRING(sound_id));
207    sf_parms = cd_set_replace(env, sf_parms, sf_speed_key, CD_DOUBLE(speed));
208    eet_snd_reader = remix_new(env, sf_plugin, sf_parms);
209
210    //free the sf_parms as it is no more needed.
211    cd_set_free(env, sf_parms);
212
213    return eet_snd_reader;
214 }
215
216 static RemixBase *
217 edje_remix_sample_create(Multisense_Data *msdata, Edje_File *file, Edje_Sample_Action *action)
218 {
219    RemixBase *remix_snd = NULL;
220    Edje_Sound_Sample *sample;
221    int i;
222    char snd_id_str[16];
223
224    if ((!file) || (!file->sound_dir))
225      return NULL;
226
227    for (i = 0; i < (int)file->sound_dir->samples_count; i++)
228      {
229         sample = &file->sound_dir->samples[i];
230         if (sample && !strcmp(sample->name, action->sample_name))
231           {
232              snprintf(snd_id_str, sizeof(snd_id_str), "edje/sounds/%i", sample->id);
233              remix_snd = eet_sound_reader_get(msdata->msenv, file->path,
234                                               snd_id_str, action->speed);
235              break;
236           }
237      }
238    return remix_snd;
239 }
240
241 static RemixBase *
242 edje_remix_tone_create(Multisense_Data *msdata, Edje_File *file, Edje_Tone_Action *action)
243 {
244    Edje_Sound_Tone *tone;
245    RemixSquareTone *square = NULL;
246    unsigned int i;
247
248    if ((!file) || (!file->sound_dir))
249      return NULL;
250
251    for (i = 0; i < file->sound_dir->tones_count; i++)
252      {
253         tone = &file->sound_dir->tones[i];
254         if (tone && !strcmp(tone->name, action->tone_name))
255           {
256              square = remix_squaretone_new (msdata->msenv->remixenv, tone->value);
257              break;
258           }
259      }
260    return square;
261 }
262
263 static Eina_Bool
264 sound_command_handler(Multisense_Data *msdata)
265 {
266    RemixCount length;
267    Edje_Multisense_Sound_Action command = {0,};
268    RemixBase *base = NULL;
269    RemixBase *sound;
270    int read_len = sizeof(command);
271
272    //read and handle all samples to avoid queue effect
273    while(read(command_pipe[0], &command, sizeof(command)) == read_len)
274      {
275         switch (command.action)
276           {
277               case EDJE_PLAY_SAMPLE:
278                  base = edje_remix_sample_create(msdata, command.snd_file,
279                                         &command.type.sample);
280                  length = remix_length(msdata->msenv->remixenv, base);
281                  edje_multisense_create_timer();
282                  break;
283               case EDJE_PLAY_TONE:
284                  base = edje_remix_tone_create(msdata, command.snd_file, &command.type.tone);
285                  length = (command.type.tone.duration *
286                               remix_get_samplerate(msdata->msenv->remixenv));
287                  break;
288               case EDJE_CLOSE_HANDLE:
289                  remix_reset(msdata->msenv->remixenv, msdata->player);
290                  return EINA_TRUE;
291                  break;
292               case EDJE_SOUND_LAST:
293                  return EINA_FALSE;
294               default:
295                  ERR("Invalid Sound Play Command\n");
296                  break;
297           }
298         if (base)
299           {
300              sound = remix_sound_new(msdata->msenv->remixenv, base, msdata->snd_layer,
301                                 REMIX_SAMPLES(msdata->offset),
302                                 REMIX_SAMPLES(length));
303              if (msdata->remaining < length) msdata->remaining = length;
304              msdata->snd_src_list = eina_list_append(msdata->snd_src_list, sound);
305              msdata->snd_src_list = eina_list_append(msdata->snd_src_list, base);
306           }
307      }
308      return EINA_TRUE;
309 }
310 #endif
311
312 #ifdef ENABLE_MULTISENSE
313 // msdata outside of thread due to thread issues in dlsym etc.
314 static Multisense_Data *msdata = NULL;
315
316 static void
317 _msdata_free(void)
318 {
319    // cleanup msdata outside of thread due to thread issues in dlsym etc.
320    if (!msdata) return;
321
322 #ifdef HAVE_LIBREMIX
323    //cleanup Remix stuffs
324    remix_destroy(msdata->msenv->remixenv, msdata->player);
325    remix_destroy(msdata->msenv->remixenv, msdata->deck);
326    remix_purge(msdata->msenv->remixenv);
327 #endif
328    free(msdata->msenv);
329    free(msdata);
330    msdata = NULL;
331 }
332
333 static void
334 _player_job(void *data __UNUSED__, Ecore_Thread *th)
335 {
336    fd_set wait_fds;
337 #ifdef HAVE_LIBREMIX
338    RemixBase *sound;
339    RemixCount process_len;
340 #endif
341 // disable and move outside of thread due to dlsym etc. thread issues
342 //   Multisense_Data * msdata = init_multisense_environment();
343
344    if (!msdata) return;
345
346    fcntl(command_pipe[0], F_SETFL, O_NONBLOCK);
347    FD_ZERO(&wait_fds);
348    FD_SET(command_pipe[0], &wait_fds);
349
350    while (!ecore_thread_check(th))
351      {
352 #ifdef HAVE_LIBREMIX
353         if (!msdata->remaining)
354           {
355              int err;
356              //Cleanup already played sound sources
357              EINA_LIST_FREE(msdata->snd_src_list, sound)
358                {
359                   remix_destroy(msdata->msenv->remixenv, sound);
360                }
361              //wait for new sound
362              err = select(command_pipe[0] + 1, &wait_fds, NULL, NULL, 0);
363           }
364         //read sound command , if any
365         if (!sound_command_handler(msdata)) break;
366         process_len = MIN(msdata->remaining, SND_PROCESS_LENGTH);
367         remix_process(msdata->msenv->remixenv, msdata->deck, process_len,
368                       RemixNone, RemixNone);
369         msdata->offset += process_len;
370         msdata->remaining -= process_len;
371 #endif
372      }
373
374 #ifdef HAVE_LIBREMIX
375    //Cleanup last played sound sources
376    EINA_LIST_FREE(msdata->snd_src_list, sound)
377      {
378         remix_destroy(msdata->msenv->remixenv, sound);
379      }
380 #endif
381    _msdata_free();
382    player_thread = NULL;
383    close(command_pipe[0]);
384    close(command_pipe[1]);
385
386    /* Lock mutex and signal the main thread to resume shutdown */
387    eina_lock_take(&eina_player_mutex);
388    eina_condition_signal(&eina_player_cond);
389    eina_lock_release(&eina_player_mutex);
390 }
391 #endif
392
393 Eina_Bool
394 _edje_multisense_internal_sound_sample_play(Edje *ed, const char *sample_name, const double speed)
395 {
396    ssize_t size = 0;
397 #if defined(ENABLE_MULTISENSE) && defined(HAVE_LIBREMIX)
398    Edje_Multisense_Sound_Action command = {0,};
399
400    if ((!pipe_initialized) && (!player_thread)) return EINA_FALSE;
401    if (!sample_name)
402      {
403         ERR("Given Sample Name is NULL\n");
404         return EINA_FALSE;
405      }
406
407    command.action = EDJE_PLAY_SAMPLE;
408    command.snd_file = ed->file;
409    strncpy(command.type.sample.sample_name, sample_name, BUF_LEN);
410    command.type.sample.speed = speed;
411    size = write(command_pipe[1], &command, sizeof(command));
412 #else
413    // warning shh
414    (void) ed;
415    (void) sample_name;
416    (void) speed;
417 #endif
418    return (size == sizeof(Edje_Multisense_Sound_Action));
419 }
420
421 Eina_Bool
422 _edje_multisense_internal_sound_tone_play(Edje *ed, const char *tone_name, const double duration)
423 {
424    ssize_t size = 0;
425 #if defined(ENABLE_MULTISENSE) && defined(HAVE_LIBREMIX)
426    Edje_Multisense_Sound_Action command = {0,};
427
428    if ((!pipe_initialized) && (!player_thread)) return EINA_FALSE;
429    if (!tone_name)
430      {
431         ERR("Given Tone Name is NULL\n");
432         return EINA_FALSE;
433      }
434
435    command.action = EDJE_PLAY_TONE;
436    command.snd_file = ed->file;
437    strncpy(command.type.tone.tone_name, tone_name, BUF_LEN);
438    command.type.tone.duration = duration;
439    size = write(command_pipe[1], &command, sizeof(command));
440 #else
441    // warning shh
442    (void) ed;
443    (void) duration;
444    (void) tone_name;
445 #endif
446    return (size == sizeof(Edje_Multisense_Sound_Action));
447
448 }
449
450 /* Initialize the modules in main thread. to avoid dlopen issue in the Threads */
451 void
452 _edje_multisense_init(void)
453 {
454 #ifdef ENABLE_MULTISENSE
455    if (!pipe_initialized && (pipe(command_pipe) != -1))
456      pipe_initialized = EINA_TRUE;
457    multisense_init = EINA_TRUE;
458
459    // init msdata outside of thread due to thread issues in dlsym etc.
460    if (!msdata) msdata = init_multisense_environment();
461
462    // Initialize the synchronisation elements with main thread.
463    eina_lock_new(&eina_player_mutex);
464    eina_condition_new(&eina_player_cond, &eina_player_mutex);
465
466    if (!player_thread)
467      player_thread = ecore_thread_feedback_run(_player_job, NULL, NULL, NULL,
468                                                NULL, EINA_TRUE);
469 #endif
470 }
471
472 void
473 _edje_multisense_shutdown(void)
474 {
475 #ifdef ENABLE_MULTISENSE
476    Edje_Multisense_Sound_Action command = {0,};
477    MULTISENSE_FACTORY_SHUTDOWN_FUNC multisense_factory_shutdown;
478
479    // lock the synchronisation mutex and hold it.
480    eina_lock_take(&eina_player_mutex);
481
482    if (m) multisense_factory_shutdown
483             = eina_module_symbol_get(m,"multisense_factory_shutdown");
484    if (multisense_factory_shutdown && msdata)
485          multisense_factory_shutdown(msdata->msenv);
486    /* Disable cancel call and exit thread function on recieving EDJE_SOUND_LAST
487    at other end of pipe */
488    //if (player_thread) ecore_thread_cancel(player_thread);
489    if (pipe_initialized)
490      {
491         //unblock the select() in player worker thread
492         command.action = EDJE_SOUND_LAST;
493         write(command_pipe[1], &command, sizeof(command));
494      }
495
496    // kill the timer if alive still.
497    edje_multisense_kill_timer();
498
499    // wait for edje multisense player to quit.
500    eina_condition_wait(&eina_player_cond);
501    eina_lock_release(&eina_player_mutex);
502
503    // cleanup the synchronisation elements.
504    eina_condition_free(&eina_player_cond);
505    eina_lock_free(&eina_player_mutex);
506 #endif
507 }