Use customized tts voice properties with priority over default platform settings 83/291483/1
authorLukasz Oleksak <l.oleksak@samsung.com>
Wed, 29 Mar 2023 10:50:33 +0000 (12:50 +0200)
committerArtur Świgoń <a.swigon@samsung.com>
Mon, 17 Apr 2023 09:22:42 +0000 (11:22 +0200)
This change allows to controll following tts voice properties for reading
requests originated by screen-reader:

* language (NULL-auto, ANSI code of locale for supported language "ko_KR")
* voice type (0-auto, 1-male, 2-female, 3-child)
* speed (0-auto, 1-slow, ... , 15-fast)

Customized values are passed to screen-reader through vconf keys:

"db/setting/accessibility/screen_reader/tts_voice"
"db/setting/accessibility/screen_reader/tts_speed"

Note:

tts_voice key should contain ":" separated string concatenating language
and voice type like "ko_KR:2"

two above keys should be registered as internal vconf keys on the platform

Change-Id: I1de9f14228dffbcec85fcf584abc6d4cedd3057e

include/screen_reader_tts.h
include/screen_reader_vconf.h
src/screen_reader_tts.c
src/screen_reader_vconf.c

index 8662bd7e091aa4dc9ccc0b4673f5a22235af2488..c10a325714ae879c37f2a164876dc87307c18e6d 100644 (file)
@@ -8,11 +8,6 @@
 #define TEXT_CUT_SIZE 300
 #define DESCRIPTION_SPEAK_DELAY 2000
 
-extern tts_h h_tts;
-extern char *language;
-extern int voice;
-extern int speed;
-
 typedef struct {
        char *text;
        int   chunk_count;
@@ -127,4 +122,19 @@ Eina_Bool tw_pause_set(Eina_Bool pause_switch);
  */
 void tw_set_utterance_cb(utterance_cb_t utter_cb, void* user_data, utterance_cb_t deleter);
 
+/**
+ * @brief Checks if the voice is supported
+ * @param[in] language Language code (e.g. "en_US")
+ * @param[in] voice_type Voice type (as defined by TTS API: 0 - auto, 1 - male, 2 - female, 3 - child)
+ * @returns EINA_TRUE if supported, EINA_FALSE otherwise
+ */
+Eina_Bool tw_is_voice_supported(const char *language, int voice_type);
+
+/**
+ * @brief Checks if the speed of speaking is in the TTS supported speed range
+ * @param[in] speed Speed of speaking
+ * @returns EINA_TRUE if speed of speaking is in the TTS supported speed range
+ */
+Eina_Bool tw_is_speed_in_range(int speed);
+
 #endif /* SCREEN_READER_TTS_H_ */
index 19927e99a9087815e86fd1a801e47e05130d4ae1..01c856f4b89b77dd0649e06c8899de1f62153a77 100644 (file)
@@ -1,6 +1,15 @@
 #ifndef SCREEN_READER_VCONF_H_
 #define SCREEN_READER_VCONF_H_
 
+typedef Eina_Bool (*TTSVoiceValidatorCb)(const char *tts_language, int tts_voice_type);
+
+/**
+ * @brief Registers callback function for TTS Voice validation
+ *
+ * @param cb pointer to function
+ */
+void vc_tts_voice_validator_register(TTSVoiceValidatorCb cb);
+
 /**
  * @brief Sets callback functions for keys: description, haptic, keyboard_feedback and sound_feedback
  */
@@ -23,5 +32,8 @@ int vc_get_haptic(void);
 int vc_get_keyboard_feedback(void);
 int vc_get_sound_feedback(void);
 int vc_get_lcd_backlight_timeout(void);
+int vc_get_tts_voice_type(void);
+int vc_get_tts_speed(void);
+const char *vc_get_tts_language(void);
 
 #endif /* SCREEN_READER_VCONF_H_ */
index ac08ae774ef5986af6f97598c4c116af9d30e58c..e2b90635456caff7a1581bcc7a81ed8bc1d838bf 100644 (file)
@@ -50,6 +50,9 @@ typedef struct {
        Ecore_Timer *delay_timer;
        bool request_speak_do;
        bool running;
+       int tts_speed_max;
+       int tts_speed_normal;
+       int tts_speed_min;
 } TWData;
 
 /* Forward delcarations - begin */
@@ -89,6 +92,12 @@ static unsigned int get_tick_count(unsigned int offset)
        return v;
 }
 
+static int choose_speed()
+{
+       int speed = vc_get_tts_speed();
+       return tw_is_speed_in_range(speed) ? speed : TTS_SPEED_AUTO;
+}
+
 static const char *get_tts_error(int r)
 {
        switch (r) {
@@ -420,7 +429,8 @@ static int send_chunk_to_tts(TWData *tw, const char *utf8_line, int *utt_id)
 {
        DEBUG("Passing TEXT: %s to TTS", utf8_line);
        int utterance_id;
-       int ret = tts_add_text(tw->tts, utf8_line, NULL, TTS_VOICE_TYPE_AUTO, TTS_SPEED_AUTO, &utterance_id);
+
+       int ret = tts_add_text(tw->tts, utf8_line, vc_get_tts_language(), vc_get_tts_voice_type(), choose_speed(), &utterance_id);
        if (ret) {
                tts_state_e state = -1;
                switch (ret) {
@@ -876,6 +886,10 @@ void tw_init_internal(TWData *tw)
        tw->request_speak_do = false;
        tw->running = false;
 
+       if (tts_get_speed_range(tw->tts, &(tw->tts_speed_min), &(tw->tts_speed_normal), &(tw->tts_speed_max)) != 0) {
+               tw->tts_speed_min = tw->tts_speed_normal = tw->tts_speed_max = TTS_SPEED_AUTO;
+       }
+
        r = tts_set_mode(tw->tts, TTS_MODE_SCREEN_READER);
        DEBUG("Set tts mode SR %d (%s)", r, get_tts_error(r));
 
@@ -897,7 +911,9 @@ void tw_init_internal(TWData *tw)
                ERROR("Fail to set utterance completed cb (%s)", get_tts_error(r));
        }
 
-       tw->init = 1;
+       tw->init = 1; // Set now so that the below line does not cause infinite recursion
+       vc_tts_voice_validator_register(&tw_is_voice_supported);
+
        DEBUG("---------------------- TTS_init END ----------------------\n\n");
 }
 
@@ -918,6 +934,8 @@ bool tw_init(void)
 
 void tw_shutdown(void)
 {
+       vc_tts_voice_validator_register(NULL);
+
        TWData *tw = tw_get_instance();
        stop_speaking(tw);
        tts_destroy(tw->tts);
@@ -981,3 +999,55 @@ Eina_Bool tw_pause_set(Eina_Bool pause_switch)
        }
        return EINA_TRUE;
 }
+
+struct SupportedVoiceCallbackData {
+       const char *const language; // in
+       const int voice_type; //in
+       Eina_Bool is_supported; // out
+};
+
+static bool supported_voice_callback(tts_h tts, const char *language, int voice_type, void *user_data)
+{
+       struct SupportedVoiceCallbackData *data = user_data;
+       bool keep_going = true;
+
+       if (!g_strcmp0(language, data->language) && (data->voice_type == 0 || voice_type == data->voice_type)) {
+               data->is_supported = EINA_TRUE;
+               keep_going = false;
+       }
+
+       return keep_going;
+}
+
+Eina_Bool tw_is_voice_supported(const char *language, int voice_type)
+{
+       if (!language || voice_type < 0)
+               return EINA_FALSE;
+
+       TWData *tw = tw_get_instance();
+       struct SupportedVoiceCallbackData data = {
+               .language = language,
+               .voice_type = voice_type,
+               .is_supported = EINA_FALSE,
+       };
+
+       int status = tts_foreach_supported_voices(tw->tts, &supported_voice_callback, &data);
+       if (status != 0)
+               ERROR("tts_foreach_supported_voices failed with code %d", status);
+
+       if (data.is_supported)
+               DEBUG("Language %s with voice_type %d is supported", language, voice_type);
+       else
+               WARNING("Language %s with voice_type %d is NOT supported", language, voice_type);
+
+       return data.is_supported;
+}
+
+Eina_Bool tw_is_speed_in_range(int speed)
+{
+       TWData *tw = tw_get_instance();
+       Eina_Bool in_range = tw->tts_speed_min <= speed && speed <= tw->tts_speed_max;
+       if (!in_range)
+               DEBUG("Speed %d is not in range (%d, %d)", speed, tw->tts_speed_min, tw->tts_speed_max);
+       return in_range;
+}
index 1068545bf4fa8794f0b78d335ca0baf2da6c002b..36dbed392230e854ae2a5d499239bfa4e8af3f57 100644 (file)
@@ -29,6 +29,8 @@
 #define VCKEY_HAPTIC            "db/setting/accessibility/screen_reader/haptic"
 #define VCKEY_KEYBOARD_FEEDBACK "db/setting/accessibility/screen_reader/keyboard_feedback"
 #define VCKEY_SOUND_FEEDBACK    "db/setting/accessibility/screen_reader/sound_feedback"
+#define VCKEY_TTS_VOICE         "db/setting/accessibility/screen_reader/tts_voice"
+#define VCKEY_TTS_SPEED         "db/setting/accessibility/screen_reader/tts_speed"
 #define VCKEY_LCD_BACKLIGHT_NORMAL "db/setting/lcd_backlight_normal"
 
 typedef struct {
@@ -38,8 +40,14 @@ typedef struct {
        int keyboard_feedback;
        int sound_feedback;
        int lcd_backlight_timeout;
+       int tts_speed;
+       int tts_voice_type;
+       char *tts_language;
+       TTSVoiceValidatorCb tts_voice_validator_cb;
 } VConfData;
 
+static VConfData *vc_get_instance(void);
+
 static int display_language_changed_cb(void *event_info, void *data)
 {
        DEBUG("START");
@@ -76,6 +84,28 @@ static void vc_get_key_values(void)
        DEBUG("END");
 }
 
+static void vcwrap_update_derived_fields(void *destination)
+{
+       VConfData *vconf_data = vc_get_instance();
+       if (destination == &(vconf_data->tts_language)) {
+               const char *voice_type = ""; // Pointer to '\0'
+               char *separator = vconf_data->tts_language ? strchr(vconf_data->tts_language, ':') : NULL;
+               if (separator) {
+                       *separator = '\0'; // Cut the string short
+                       voice_type = separator + 1; // Valid even if ':' was the last char -- then points to '\0'
+               }
+
+               vconf_data->tts_voice_type = atoi(voice_type); // atoi() returns 0 on failure
+
+               if (vconf_data->tts_voice_validator_cb && !vconf_data->tts_voice_validator_cb(vconf_data->tts_language, vconf_data->tts_voice_type)) {
+                       free(vconf_data->tts_language);
+                       // fallback to auto-selection of platform default language and voice_type in case customized values are not valid
+                       vconf_data->tts_language = NULL;
+                       vconf_data->tts_voice_type = 0;
+               }
+       }
+}
+
 static int vcwrap_get_key_int(const char *key, int def)
 {
        int result = def;
@@ -85,11 +115,36 @@ static int vcwrap_get_key_int(const char *key, int def)
        return result;
 }
 
+// Note: free() the result in either case
+static char *vcwrap_get_key_str(const char *key, const char *def)
+{
+       char *result = vconf_get_str(key); // already strdup()'ed
+       if (!result) {
+               ERROR("vconf_get_str failed! key=%s", key);
+               return def ? strdup(def) : NULL;
+       }
+       return result;
+}
+
 static void vcwrap_field_updater_int(keynode_t *node, void *destination)
 {
        if (!destination)
                return;
        *((int*)destination) = vconf_keynode_get_int(node);
+       vcwrap_update_derived_fields(destination);
+}
+
+static void vcwrap_field_updater_str(keynode_t *node, void *destination)
+{
+       if (!destination)
+               return;
+
+       const char *node_str = vconf_keynode_get_str(node);
+       char **dest_ptr = (char **)destination;
+
+       free(*dest_ptr);
+       *dest_ptr = strdup(node_str);
+       vcwrap_update_derived_fields(destination);
 }
 
 static void vcwrap_set_field_updater_int(const char *key, int *data)
@@ -98,12 +153,24 @@ static void vcwrap_set_field_updater_int(const char *key, int *data)
                ERROR("Could not create updater for key=%s", key);
 }
 
+static void vcwrap_set_field_updater_str(const char *key, char **data)
+{
+       if (vconf_notify_key_changed(key, vcwrap_field_updater_str, (void *)data))
+               ERROR("Could not create updater for key=%s", key);
+}
+
 static void vcwrap_unset_field_updater_int(const char *key)
 {
        if (vconf_ignore_key_changed(key, vcwrap_field_updater_int))
                DEBUG("Could not delete notify callback for key=%s", key);
 }
 
+static void vcwrap_unset_field_updater_str(const char *key)
+{
+       if (vconf_ignore_key_changed(key, vcwrap_field_updater_str))
+               DEBUG("Could not delete notify callback for key=%s", key);
+}
+
 static VConfData *vc_get_instance(void)
 {
        static VConfData vconf_data = {0};
@@ -119,6 +186,9 @@ static VConfData *vc_get_instance(void)
        vconf_data.keyboard_feedback = vcwrap_get_key_int(VCKEY_KEYBOARD_FEEDBACK, true);
        vconf_data.sound_feedback = vcwrap_get_key_int(VCKEY_SOUND_FEEDBACK, true);
        vconf_data.lcd_backlight_timeout = vcwrap_get_key_int(VCKEY_LCD_BACKLIGHT_NORMAL, -1);
+       vconf_data.tts_speed = vcwrap_get_key_int(VCKEY_TTS_SPEED, 0);
+       vconf_data.tts_language = vcwrap_get_key_str(VCKEY_TTS_VOICE, NULL);
+       vcwrap_update_derived_fields(&(vconf_data.tts_language));
 
        vc_get_key_values();
 
@@ -129,6 +199,8 @@ static VConfData *vc_get_instance(void)
        vcwrap_set_field_updater_int(VCKEY_KEYBOARD_FEEDBACK, &(vconf_data.keyboard_feedback));
        vcwrap_set_field_updater_int(VCKEY_SOUND_FEEDBACK, &(vconf_data.sound_feedback));
        vcwrap_set_field_updater_int(VCKEY_LCD_BACKLIGHT_NORMAL, &(vconf_data.lcd_backlight_timeout));
+       vcwrap_set_field_updater_int(VCKEY_TTS_SPEED, &(vconf_data.tts_speed));
+       vcwrap_set_field_updater_str(VCKEY_TTS_VOICE, &(vconf_data.tts_language));
 
        DEBUG("---------------------- VCONF_init END ----------------------\n\n");
 
@@ -152,6 +224,11 @@ void vc_exit(void)
        vcwrap_unset_field_updater_int(VCKEY_DESCRIPTION);
        vcwrap_unset_field_updater_int(VCKEY_SOUND_FEEDBACK);
        vcwrap_unset_field_updater_int(VCKEY_LCD_BACKLIGHT_NORMAL);
+       vcwrap_unset_field_updater_int(VCKEY_TTS_SPEED);
+       vcwrap_unset_field_updater_str(VCKEY_TTS_VOICE);
+
+       free(vconf_data->tts_language);
+       vconf_data->tts_language = NULL;
 }
 
 int vc_get_read_description(void)
@@ -179,3 +256,26 @@ int vc_get_lcd_backlight_timeout(void)
        return vc_get_instance()->lcd_backlight_timeout;
 }
 
+int vc_get_tts_speed(void)
+{
+       return vc_get_instance()->tts_speed;
+}
+
+int vc_get_tts_voice_type(void)
+{
+       return vc_get_instance()->tts_voice_type;
+}
+
+const char *vc_get_tts_language(void)
+{
+       return vc_get_instance()->tts_language;
+}
+
+void vc_tts_voice_validator_register(TTSVoiceValidatorCb cb)
+{
+       VConfData *vconf_data = vc_get_instance();
+
+       vconf_data->tts_voice_validator_cb = cb;
+       if (cb)
+               vcwrap_update_derived_fields(&(vconf_data->tts_language));
+}
\ No newline at end of file