/************************************************************************************************** * * Copyright (c) 2019-2026 Axera Semiconductor (Ningbo) Co., Ltd. All Rights Reserved. * * This source file is the property of Axera Semiconductor (Ningbo) Co., Ltd. and * may not be copied or distributed in any isomorphic form without the prior * written consent of Axera Semiconductor (Ningbo) Co., Ltd. * **************************************************************************************************/ #include "frontend/kokoro_frontend.hpp" #include "utils/logger.h" #include "utils/string_utils.hpp" #include "text_cleaner/text_cleaner.hpp" #include "text_normalizer/text_normalizer.hpp" #include "g2p/EnEspeakG2P.hpp" #include "g2p/LatinEspeakG2P.hpp" #include "text_processor/jieba_processor.hpp" #include "g2p/ZhJiebaG2P.hpp" #include using namespace std; using namespace utils; // Impl class KokoroFrontend::Impl { public: Impl() = default; ~Impl() = default; bool init(AX_TTS_INIT_CONFIG* config) { if (strlen(config->espeak_data_path) == 0) { ALOGE("espeak_data_path is not set in config"); return false; } espeak_data_path_ = string(config->espeak_data_path); jieba_dict_path_ = string(config->jieba_dict_path); if (jieba_dict_path_.empty()) { ALOGW("jieba_dict_path is empty; zh will be unavailable until set."); } // Preload g2p: English only; zh is loaded lazily. g2p_backends_.insert({"en", make_shared(espeak_data_path_.c_str())}); return true; } std::vector run(const std::string& input_text, const std::string& language, const std::map& vocab, int& err) { vector tokens; if (language == "zh") { if (!ensure_zh_ready_()) { ALOGE("Zh processor not ready!"); err = -1; return {}; } } static const std::string kPhonemePrefix = "__PHONEMES__:"; std::string phonemes; if (input_text.rfind(kPhonemePrefix, 0) == 0) { phonemes = input_text.substr(kPhonemePrefix.size()); } else { // 1. Clean text (skip aggressive cleaning for Japanese to preserve fullwidth punctuation) auto cleaned_text = (language == "ja") ? input_text : text_cleaner_.run(input_text); // 2. Normalize text auto normalized_text = text_normalizer_.run(cleaned_text, language); // 3. G2P auto g2p = load_g2p_(language); phonemes = g2p->run(normalized_text, err); } ALOGD("input_text: %s", input_text.c_str()); ALOGD("phonemes: %s", phonemes.c_str()); // 4. Phonemes -> Tokens tokens.reserve(phonemes.length() + 2); tokens.emplace_back(0); vector chars = utils::split_utf8(phonemes); for (const auto& c : chars) { if (vocab.count(c)) tokens.emplace_back(vocab.at(c)); } tokens.emplace_back(0); return tokens; } private: shared_ptr load_g2p_(const string& language) { if (g2p_backends_.find(language) == g2p_backends_.end()) { if (language == "zh") { if (!ensure_zh_ready_()) { return nullptr; } } shared_ptr new_g2p = make_shared( espeak_data_path_.c_str(), language ); g2p_backends_.insert({language, new_g2p}); return new_g2p; } else { return g2p_backends_.at(language); } } bool ensure_zh_ready_() { if (zh_ready_) { return true; } std::lock_guard lock(zh_mutex_); if (zh_ready_) { return true; } if (jieba_dict_path_.empty()) { ALOGE("jieba_dict_path is empty, cannot init zh processor"); return false; } zh_processor_ = make_shared(); if (!zh_processor_->load_dict(jieba_dict_path_)) { ALOGE("Init zh_processor failed!"); return false; } text_normalizer_.set_zh_processor(zh_processor_); g2p_backends_.insert({"zh", make_shared(zh_processor_)}); zh_ready_ = true; return true; } private: string espeak_data_path_; string jieba_dict_path_; map> g2p_backends_; shared_ptr zh_processor_; TextCleaner text_cleaner_; TextNormalizer text_normalizer_; std::mutex zh_mutex_; bool zh_ready_ = false; }; // KokoroFrontend KokoroFrontend::KokoroFrontend(): impl_(std::make_unique()) { } KokoroFrontend::~KokoroFrontend() { impl_.reset(); } bool KokoroFrontend::init(AX_TTS_INIT_CONFIG* config) { return impl_->init(config); } std::vector KokoroFrontend::run(const std::string& input_text, const std::string& language, const std::map& vocab, int& err) { return impl_->run(input_text, language, vocab, err); }