Qt C++项目集成MeloTTS.cpp:纯C++离线TTS引擎的完整实践指南

发布时间:2026/7/14 6:33:18

Qt C++项目集成MeloTTS.cpp:纯C++离线TTS引擎的完整实践指南 1. 项目概述为什么要在Qt C项目中集成TTS在桌面应用开发中尤其是教育软件、辅助工具、工业监控系统或者需要语音播报的客户端程序里文本转语音TTS功能常常是提升用户体验、实现信息无障碍访问的关键一环。你可能正在开发一个需要朗读通知、播报数据或者为视障用户提供支持的Qt应用这时一个稳定、高效且易于集成的TTS方案就成了刚需。Qt作为一个强大的跨平台C框架其核心优势在于“一次编写到处编译”。然而Qt本身并没有内置原生的、跨平台的TTS模块。这意味着如果你想让你的Qt应用“开口说话”就必须自己动手寻找并集成一个合适的TTS引擎。这个过程说简单也简单说复杂也复杂。简单在于市面上有不少成熟的库和方案复杂在于你需要权衡跨平台兼容性、语音质量、部署复杂度、许可证等一系列问题。最近一个名为MeloTTS.cpp的纯C TTS库进入了我的视野。它由 MyShell.ai 开源主打轻量级和跨平台这听起来简直是Qt项目的绝配。纯C实现意味着没有额外的语言运行时依赖比如Python可以轻松地编译进你的Qt项目轻量级意味着对最终发布的应用体积影响小。这让我决定以它为蓝本深入探索在Qt C环境中搭建一套完整、可用的TTS系统。本文将不仅仅是一个简单的“Hello World”示例而是会拆解从库的选择、编译集成、核心API的使用到实际项目中的封装设计、性能优化和跨平台部署的完整心路历程并分享我踩过的那些坑。2. 核心方案选型为什么是纯C TTS库当你决定为Qt项目添加TTS功能时面前通常有几条路使用操作系统原生API比如在Windows上用SAPISpeech API在macOS上用NSSpeechSynthesizer在Linux上用eSpeak或Festival配合DBus。这条路的最大优点是“原生”通常无需额外分发库文件。但缺点也同样明显你需要为每个平台写一套不同的代码用#ifdef把逻辑割裂开维护成本高且不同平台的语音质量和功能差异巨大。集成第三方SDK例如微软的Azure Cognitive Services TTS、Google Cloud Text-to-Speech等。它们能提供顶尖的语音质量但严重依赖网络不适合离线场景且涉及API调用费用和隐私考量。使用跨平台的C库这正是我们寻找的目标。一个理想的库应该像Qt一样用同一套C代码在各大主流桌面操作系统上运行。在评估了多个选项后MeloTTS.cpp的以下几个特点让我最终选择了它纯C零外部运行时依赖这是最吸引人的一点。它不依赖Python、Java或其他解释器。你可以把它当作一个普通的第三方库通过CMake或直接引入源码的方式集成到你的Qt项目中最终编译成一个独立的可执行文件部署极其简单。轻量级与离线运行模型和数据文件相对较小相比一些动辄几百MB的TTS引擎支持完全离线合成这对数据安全有要求或网络环境不稳定的应用场景至关重要。基于现代TTS技术它并非简单的拼接合成而是基于类似VITS的端到端神经网络模型虽然轻量但合成的语音自然度和流畅度远超传统的eSpeak更接近商业级云服务的听感。宽松的开源许可证通常采用MIT或Apache 2.0等宽松协议可以放心用于商业项目。当然它也有局限性。例如相比云服务其音色选择可能较少在多语言混合朗读、复杂情感控制方面可能较弱。但对于大多数桌面应用的通知、播报、阅读辅助等场景它已经绰绰有余。注意选择TTS库时务必仔细阅读其许可证License确认是否允许商业闭源使用。同时要评估其模型文件的版权确保你有权分发和使用这些语音数据。3. 环境准备与库的集成确定了核心库下一步就是把它“请进”我们的Qt项目。这里我以使用CMake构建系统的Qt项目为例因为这是现代Qt项目特别是Qt6推荐的方式也最便于管理第三方依赖。3.1 获取MeloTTS.cpp首先你需要获取MeloTTS.cpp的源代码。通常是从GitHub仓库克隆或下载发布包。# 在你的项目根目录下创建一个third_party文件夹来存放第三方库 mkdir -p third_party cd third_party git clone https://github.com/myshell-ai/MeloTTS.cpp.git克隆后目录结构大致如下your_qt_project/ ├── CMakeLists.txt ├── src/ └── third_party/ └── MeloTTS.cpp/ ├── CMakeLists.txt ├── include/ ├── src/ └── ...3.2 集成到Qt项目的CMake中这是最关键的一步目标是在你的主CMakeLists.txt中将MeloTTS.cpp作为一个子目录subdirectory或通过FetchContent引入并正确链接。方案一使用add_subdirectory推荐便于调试在你的项目主CMakeLists.txt文件中添加cmake_minimum_required(VERSION 3.16) project(MyQtTTSApp LANGUAGES CXX) # 设置C标准MeloTTS.cpp可能需要C17或更高 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找Qt包这里以Qt6为例 find_package(Qt6 REQUIRED COMPONENTS Core Widgets) # 添加你的项目源文件 add_executable(MyQtTTSApp src/main.cpp src/mainwindow.cpp # ... 其他源文件 ) # 链接Qt库 target_link_libraries(MyQtTTSApp Qt6::Core Qt6::Widgets ) # 关键步骤添加MeloTTS.cpp子目录 add_subdirectory(third_party/MeloTTS.cpp) # 将MeloTTS.cpp的头文件目录包含进来 target_include_directories(MyQtTTSApp PRIVATE third_party/MeloTTS.cpp/include ) # 链接MeloTTS.cpp库。注意库名需要查看MeloTTS.cpp自身的CMakeLists.txt来确定。 # 通常它会被命名为 melo-tts 或 MeloTTS。 target_link_libraries(MyQtTTSApp PRIVATE melo-tts) # 如果你需要模型文件可能需要指定模型路径或复制模型到构建目录 # 假设模型文件在 third_party/MeloTTS.cpp/models 下 file(COPY third_party/MeloTTS.cpp/models DESTINATION ${CMAKE_CURRENT_BINARY_DIR})方案二使用FetchContent自动下载如果你希望构建时自动从网络获取最新代码可以使用CMake的FetchContent模块。但这要求项目始终有网且不利于版本锁定。include(FetchContent) FetchContent_Declare( melotts_cpp GIT_REPOSITORY https://github.com/myshell-ai/MeloTTS.cpp.git GIT_TAG main # 建议指定一个具体的tag或commit如 v1.0.0 ) FetchContent_MakeAvailable(melotts_cpp) # 后续的 target_include_directories 和 target_link_libraries 与方案一类似实操心得在集成初期强烈建议使用add_subdirectory方案。这样当MeloTTS.cpp编译出错或你需要跟踪调试时可以直接在third_party目录下修改和调试源码非常方便。等一切稳定后再考虑将其预编译为静态库或使用包管理器管理。3.3 解决可能的编译依赖MeloTTS.cpp底层可能依赖一些数学计算库如OpenBLAS、MKL或神经网络推理库如ONNX Runtime、ggml。你需要根据它的编译说明安装相应的依赖。例如在Ubuntu/Debian上你可能需要sudo apt-get update sudo apt-get install -y libopenblas-dev libomp-dev在Windows上你可能需要通过vcpkg或手动安装这些库。务必仔细阅读MeloTTS.cpp仓库根目录的README.md或INSTALL.md文件这是避免编译错误的最快途径。一个常见的坑是多线程库。在Linux上你可能需要在CMakeLists.txt中显式链接pthreadtarget_link_libraries(MyQtTTSApp PRIVATE melo-tts pthread)3.4 获取并放置模型文件TTS库的核心是模型。MeloTTS.cpp需要对应的语音模型文件通常是.bin或.onnx等格式。你需要从其项目页面或Hugging Face等模型仓库下载指定的模型。假设你下载了melotts_model.bin和melotts_config.json。你需要将它们放在一个你的应用程序可以找到的路径。通常有两种做法随程序分发将模型文件放在项目资源目录如resources/models/并使用CMake的file(COPY...)命令在构建时复制到输出目录如上面示例所示。在代码中使用相对路径如./models/melotts_model.bin或Qt的资源系统qrc来定位。运行时指定路径提供一个设置界面让用户自定义模型文件路径。程序启动时从配置文件或用户输入中读取该路径。对于第一个正式可用的版本推荐方法1因为它最简单可靠。4. 核心API封装与Qt的融合现在库已经集成好了我们可以开始写代码了。但直接在主业务逻辑里调用MeloTTS.cpp的C风格或低级C API会显得很杂乱也不利于维护和跨线程操作。最佳实践是封装一个专门的TTS管理类。4.1 设计TTS引擎管理器TtsEngine我们将创建一个TtsEngine类它负责初始化MeloTTS.cpp引擎。加载模型。提供简单的speak(const QString text)接口。管理语音合成的状态如是否正在播放。安全地处理跨线程调用因为TTS合成可能在后台线程进行而播放需要在主线程或音频线程。头文件ttsengine.h#ifndef TTSENGINE_H #define TTSENGINE_H #include QObject #include QString #include memory // 前向声明避免暴露MeloTTS.cpp的内部头文件 struct MeloTTS_Instance; class TtsEngine : public QObject { Q_OBJECT public: explicit TtsEngine(QObject *parent nullptr); ~TtsEngine(); // 初始化引擎指定模型路径。成功返回true。 bool initialize(const QString modelPath); // 合成并播放文本。非阻塞立即返回。 void speak(const QString text); // 停止当前播放。 void stop(); // 检查引擎是否正在播放。 bool isSpeaking() const; signals: // 信号当开始播放时发出。 void started(); // 信号当播放完成时发出。 void finished(); // 信号当发生错误时发出。 void errorOccurred(const QString errorMessage); private: // 私有实现细节使用Pimpl模式隐藏第三方库的具体类型。 struct Impl; std::unique_ptrImpl pImpl; }; #endif // TTSENGINE_H4.2 实现TTS引擎管理器TtsEngine源文件ttsengine.cpp这里我们使用PimplPointer to Implementation模式将MeloTTS.cpp的所有头文件包含和具体操作隔离在实现类中保持接口的干净和编译依赖的最小化。#include ttsengine.h #include QDebug #include QThread // 包含MeloTTS.cpp的头文件 extern C { #include melo_tts.h // 假设这是它的主头文件具体名称需查看其源码 } struct TtsEngine::Impl { MeloTTS_Instance* ttsInstance nullptr; bool isInitialized false; volatile bool isPlaying false; // 简单的状态标志注意线程安全 }; TtsEngine::TtsEngine(QObject *parent) : QObject(parent) , pImpl(std::make_uniqueImpl()) { } TtsEngine::~TtsEngine() { stop(); if (pImpl-ttsInstance) { melo_tts_destroy(pImpl-ttsInstance); // 假设有销毁函数 pImpl-ttsInstance nullptr; } } bool TtsEngine::initialize(const QString modelPath) { if (pImpl-isInitialized) { qWarning() TTS engine is already initialized.; return true; } // 将QString转换为C风格的字符串 QByteArray modelPathBa modelPath.toLocal8Bit(); const char* modelPathCStr modelPathBa.constData(); // 调用MeloTTS.cpp的初始化函数 // 注意这里的函数名和参数是示例务必查阅MeloTTS.cpp的实际API文档 pImpl-ttsInstance melo_tts_create(modelPathCStr); if (!pImpl-ttsInstance) { QString err QString(Failed to initialize TTS engine with model: %1).arg(modelPath); qCritical() err; emit errorOccurred(err); return false; } pImpl-isInitialized true; qInfo() TTS engine initialized successfully.; return true; } void TtsEngine::speak(const QString text) { if (!pImpl-isInitialized) { emit errorOccurred(TTS engine is not initialized.); return; } if (text.isEmpty()) { return; } // 为了避免阻塞UI在一个单独的线程中执行合成任务 QtConcurrent::run([this, text]() { // 设置播放状态 pImpl-isPlaying true; emit started(); QByteArray textBa text.toUtf8(); // TTS引擎通常需要UTF-8 const char* textCStr textBa.constData(); // 示例合成语音并播放。这里假设 melo_tts_speak 是同步阻塞函数。 // 实际情况可能更复杂可能需要先合成到缓冲区再用音频API播放。 int result melo_tts_speak(pImpl-ttsInstance, textCStr); // 播放完成 pImpl-isPlaying false; emit finished(); if (result ! 0) { QString err QString(TTS synthesis failed with code: %1).arg(result); qWarning() err; // 注意信号需要在对象所在的线程发射这里假设TtsEngine对象在主线程。 // 更严谨的做法是使用QMetaObject::invokeMethod进行线程间调用。 emit errorOccurred(err); } }); } void TtsEngine::stop() { // 设置标志位请求停止如果API支持 pImpl-isPlaying false; // 如果有停止函数则调用。例如melo_tts_stop(pImpl-ttsInstance); } bool TtsEngine::isSpeaking() const { return pImpl-isPlaying; }重要提示上面的melo_tts_speak函数是假设的。真实的MeloTTS.cpp API很可能不是这样它可能将合成和播放分开。一个更常见的流程是melo_tts_generate_audio(tts, text, audio_buffer, samples)将文本合成到音频缓冲区。然后使用一个音频播放库如SDL2、PortAudio或Qt自身的QAudioOutput来播放这个缓冲区。你必须根据MeloTTS.cpp提供的真实API文档来调整上述实现。核心思想不变封装、异步、状态管理。4.3 在Qt界面中调用封装好TtsEngine后在Qt的界面代码如MainWindow中使用它就非常清晰了。// mainwindow.cpp #include mainwindow.h #include ui_mainwindow.h #include ttsengine.h #include QMessageBox MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , m_ttsEngine(new TtsEngine(this)) // 创建引擎实例 { ui-setupUi(this); // 连接信号 connect(m_ttsEngine, TtsEngine::started, this, [](){ qDebug() TTS started speaking; }); connect(m_ttsEngine, TtsEngine::finished, this, [](){ qDebug() TTS finished; }); connect(m_ttsEngine, TtsEngine::errorOccurred, this, [](const QString err){ QMessageBox::critical(nullptr, TTS Error, err); }); // 初始化引擎 QString modelPath QCoreApplication::applicationDirPath() /models/melotts_model.bin; if (!m_ttsEngine-initialize(modelPath)) { QMessageBox::warning(this, Warning, Failed to init TTS engine. Speech function disabled.); } // 连接按钮点击事件 connect(ui-speakButton, QPushButton::clicked, this, MainWindow::onSpeakButtonClicked); connect(ui-stopButton, QPushButton::clicked, m_ttsEngine, TtsEngine::stop); } MainWindow::~MainWindow() { delete ui; } void MainWindow::onSpeakButtonClicked() { QString textToSpeak ui-textEdit-toPlainText(); if (!textToSpeak.isEmpty()) { m_ttsEngine-speak(textToSpeak); ui-statusBar-showMessage(Synthesizing and playing..., 2000); } }通过这样的封装我们将复杂的第三方C库转换成了一个符合Qt信号槽习惯的、易于使用的QObject类。界面逻辑变得非常简洁。5. 高级功能与性能优化一个基础的TTS播放器已经完成了。但对于一个成熟的项目我们还需要考虑更多。5.1 音频播放器的选择与集成如前所述MeloTTS.cpp可能只负责合成音频数据PCM格式播放需要你自己处理。你有几个选择Qt Multimedia (QAudioOutput)这是最Qt的方式跨平台支持较好。你需要将PCM数据写入一个QIODevice如QBuffer然后由QAudioOutput播放。// 伪代码示例 QAudioFormat format; format.setSampleRate(24000); // 必须与TTS合成输出一致 format.setChannelCount(1); format.setSampleSize(16); format.setCodec(audio/pcm); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); QAudioOutput *audioOutput new QAudioOutput(format, this); QIODevice *audioDevice audioOutput-start(); // 当TTS合成完数据后 audioDevice-write(audioData, audioDataSize);SDL2一个强大的跨平台多媒体库音频处理能力更专业。需要额外链接SDL2库。PortAudio另一个专业的跨平台音频I/O库。选择哪个取决于你的项目复杂度。如果只是简单播放QAudioOutput足够如果需要更精细的控制或低延迟SDL2是更好的选择。5.2 合成队列与异步处理上面的简单实现中如果用户快速点击“朗读”按钮会启动多个并发合成线程可能导致状态混乱或资源竞争。一个更健壮的方案是引入任务队列。你可以创建一个QQueueQString来保存待合成的文本并使用一个工作线程或QtConcurrent线程池从队列中取出任务依次执行。TtsEngine::speak方法改为向队列添加任务并由一个专用的“合成播放器”线程处理。这确保了语音按顺序播放不会重叠。5.3 参数调节与语音控制基本的TTS API通常支持调节语速、音高、音量等。你的TtsEngine类应该暴露这些设置接口。class TtsEngine { // ... public slots: void setSpeed(float speed); // 0.5 到 2.0 void setPitch(float pitch); // 0.5 到 2.0 // ... };在实现里这些参数需要在调用合成函数前通过MeloTTS.cpp提供的API如melo_tts_set_speed进行设置。5.4 资源管理与内存优化TTS模型加载可能占用较多内存。如果你的应用生命周期内只需要偶尔使用TTS可以考虑懒加载和及时卸载。懒加载在第一次调用speak时才初始化引擎和加载模型。及时卸载在应用空闲一段时间后或者提供一个“释放TTS资源”的按钮主动调用销毁函数释放模型内存。6. 跨平台部署与打包让程序在你的开发机上运行只是第一步让它在用户的电脑上也能运行才是终点。这是Qt开发者的必修课集成原生库后情况更复杂一些。6.1 Windows平台Windows下最大的挑战是动态库依赖DLL。你需要找到MeloTTS.cpp编译后生成的.dll文件以及它可能依赖的运行时库如onnxruntime.dll,openblas.dll等。查找依赖使用工具Dependencies原名Dependency Walker或Visual Studio自带的dumpbin /dependents your_app.exe命令来查看可执行文件的所有依赖。收集DLL将这些DLL文件复制到你的可执行文件.exe所在的目录。模型文件将模型文件目录如models/也复制到exe同级目录。使用windeployqtQt提供了windeployqt工具可以自动抓取你的程序所需的Qt库。在命令行运行windeployqt --release path/to/your/release/folder/MyQtTTSApp.exe它会将必要的Qt DLL、插件等复制过来。测试将整个文件夹包含exe、Qt库、你的DLL、模型复制到一个干净的、没有开发环境的Windows虚拟机或电脑上运行测试。6.2 Linux平台Linux下通常使用AppImage或Snap进行分发但最简单直接的方式还是提供编译好的二进制包并说明依赖。解决动态链接你可以选择静态链接MeloTTS.cpp及其核心依赖如OpenBLAS但这可能受许可证限制且复杂。更常见的是动态链接。打包依赖使用ldd your_app查看依赖。对于非系统标准库如你自己编译的libmelo-tts.so你需要将它们打包。推荐方式AppDir AppImage创建一个目录结构如MyApp.AppDir/usr/bin/放你的可执行文件MyApp.AppDir/usr/lib/放你的.so库和Qt插件MyApp.AppDir/usr/share/放模型等资源。使用linuxdeployqt工具一个类似于windeployqt的工具来帮你自动收集Qt库和插件。最后用appimagetool将这个AppDir打包成一个独立的MyApp.AppImage文件用户下载后赋予执行权限即可运行。6.3 macOS平台macOS下使用.appbundle。创建App BundleQt Creator在Release构建时会自动生成一个.app包但可能不完整。使用macdeployqt类似Windows运行macdeployqt MyQtTTSApp.app。这个命令会将Qt库复制到MyQtTTSApp.app/Contents/Frameworks/并修正它们的链接路径。集成第三方库你需要手动将libmelo-tts.dylib及其依赖的其他.dylib文件复制到Contents/Frameworks/下。然后使用install_name_tool命令来修正这些库的id和依赖路径这是一个比较繁琐但必须的过程。模型文件将模型目录复制到Contents/Resources/下。最终检查使用otool -L MyQtTTSApp.app/Contents/MacOS/MyQtTTSApp检查所有库的路径是否正确都应该是executable_path/../Frameworks/或rpath的相对路径。踩坑实录跨平台部署最常遇到的问题就是“库找不到”。在Windows上是“无法启动此程序因为计算机中丢失VCRUNTIME140.dll”在Linux上是“error while loading shared libraries: libmelo-tts.so: cannot open shared object file”在macOS上是“Library not loaded: rpath/libxxx.dylib”。解决之道就是耐心1) 理清所有依赖链2) 将它们放到正确的位置3) 修正链接路径特别是macOS。制作一个自动化的部署脚本如CMake的CPack或自定义的Shell/Batch脚本能极大提升效率。7. 常见问题排查与调试技巧即使按照步骤操作也难免会遇到问题。这里记录一些我遇到过的典型问题及解决方法。7.1 编译错误“fatal error: ‘melo_tts.h’ file not found”原因CMake没有正确地将MeloTTS.cpp的头文件目录包含进来。解决检查target_include_directories指令确保路径正确。使用${CMAKE_CURRENT_SOURCE_DIR}/third_party/MeloTTS.cpp/include这样的绝对路径更保险。“undefined reference to melo_tts_create’…”原因链接失败。没有链接MeloTTS.cpp的库或者库文件没有正确生成。解决确认add_subdirectory后MeloTTS.cpp的目标库如melo-tts是否被成功创建。可以在构建目录下查找对应的.a或.so文件。确认target_link_libraries中写的库名与MeloTTS.cpp的CMakeLists.txt中add_library指定的名称完全一致区分大小写。C标准不匹配原因你的项目使用C14而MeloTTS.cpp需要C17。解决在你的主CMakeLists.txt中统一设置较高的标准set(CMAKE_CXX_STANDARD 17)。7.2 运行时错误程序启动崩溃提示“Failed to load model”原因模型文件路径错误、模型文件损坏或格式不匹配。解决打印出传递给初始化函数的绝对路径确认文件存在且可读。确保模型文件版本与MeloTTS.cpp库版本兼容。从官方指定渠道重新下载模型。在代码中使用QFile::exists()检查文件。合成语音速度极慢原因默认可能使用CPU进行神经网络推理。对于长文本这会很慢。解决查看MeloTTS.cpp文档是否支持GPU推理如CUDA、Metal。如果支持确保安装了对应的驱动和库并在初始化时启用。对于CPU可以尝试启用多线程或使用更优化的数学库如MKL代替OpenBLAS。在UI上给出“合成中…”的提示避免用户以为程序卡死。播放没有声音或杂音原因音频播放参数采样率、声道数、样本格式与TTS合成输出的音频数据不匹配。解决这是最常见的原因你必须精确知道MeloTTS.cpp合成出的音频PCM数据的参数采样率如24000 Hz、声道数通常是单声道1、样本格式如16位有符号整数。然后在初始化QAudioOutput或SDL音频设备时设置完全相同的参数。写一个简单的测试将合成出的音频数据保存为WAV文件WAV头PCM数据用系统播放器听听是否正常。如果文件正常但程序播放不正常问题肯定出在播放环节。中文文本合成出来是乱码或错误发音原因字符串编码问题。解决确保传递给TTS引擎的字符串是UTF-8编码。在Qt中使用QString::toUtf8().constData()转换为C字符串。同时确认你下载的模型是支持中文的模型。7.3 调试与日志启用MeloTTS.cpp的日志很多库在编译时可以通过定义宏如-DDEBUG或调用初始化函数时设置日志级别来输出详细信息。查看其文档打开日志能帮助你定位是模型加载失败、合成失败还是参数错误。使用Qt的日志系统在TtsEngine封装类中大量使用qDebug(),qInfo(),qWarning(),qCritical()输出关键步骤的状态和错误信息。分离问题当遇到问题时尝试将TTS部分剥离出来写一个最简单的、不带Qt界面的C控制台程序来测试MeloTTS.cpp的基本功能。这能帮你快速判断问题是出在TTS库本身还是出在Qt集成或音频播放环节。集成一个像MeloTTS.cpp这样的原生C库到Qt项目中是一个典型的“系统集成”工作。它考验的不仅仅是对Qt的掌握还有对CMake、跨平台编译、原生库依赖管理、音频编程等多方面知识的理解。整个过程就像拼装一台精密仪器每一步都需要仔细对照图纸文档耐心调试。但当你的Qt应用第一次清晰地用语音读出你设定的文字时那种成就感会让你觉得所有的折腾都是值得的。最重要的是你获得了一个完全离线、跨平台、可高度定制的语音能力这能为你的应用打开一扇新的大门。

相关新闻