目的:实现实时语音生成(尝试过index-tts-vllm,GPT-SoVITS,cosyvoice等模型,发现生成速度和混合文本的合成音频都不满足预期效果,注:qwen3 0.6b tts的混合文本合成效果不错)

服务细节流程参考:

​​​​​​​​​​dify TTS部署 GPT-SoVITS

这里主要说一下调用SDK步骤:

总的来说即修改demo的cpp代码,然后使用python搭建一个fastapi服务调用

1.在科大讯飞官网注册一个账号(可以免费使用10台机器90天)

2.下载SDK文件:离线语音合成​​​​​​​(新版)

SDK的readme写的很清楚,按着操作就能跑通demo

3.因为demo内的输入和输出是固定的,显然不符合部署为服务的需求,这里进行了改动。

新增头文件aikit_interface.h:

// aikit_interface.h
#ifndef AIKIT_INTERFACE_H
#define AIKIT_INTERFACE_H

#ifdef __cplusplus
extern "C" {
#endif

// 初始化函数
int AEE_Init();

// 文本转语音函数
// text: 输入文本
// output_file: 输出音频文件路径
// language: 1-中文, 2-英文
// role: 发音人角色
int TestXTTS(const char* text, const char* output_file, int language, const char* role);
// 清理资源
void AEE_Cleanup();

#ifdef __cplusplus
}
#endif

#endif
// aikit_interface.cpp
#include <fstream>
#include <atomic>
#include <unistd.h>
#include <cstring>
#include "aikit_biz_api.h"
#include "aikit_constant.h"
#include "aikit_biz_config.h"
#include "aikit_interface.h"

using namespace std;
using namespace AIKIT;

static std::atomic_bool ttsFinished(false);
FILE *fin = nullptr;
static const char *ABILITY = "e2e44feff";

// 回调函数
void OnOutput(AIKIT_HANDLE* handle, const AIKIT_OutputData* output){
    printf("OnOutput key:%s\n",(char*)output->node->key);
    printf("OnOutput status:%d\n",output->node->status);
    if((output->node->value) && (fin != nullptr)) {
        fwrite(output->node->value, sizeof(char), output->node->len, fin);
    }
}

void OnEvent(AIKIT_HANDLE* handle, AIKIT_EVENT eventType, const AIKIT_OutputEvent* eventValue){
    printf("OnEvent:%d\n",eventType);
    if(eventType == AIKIT_Event_End){
        ttsFinished = true;
    }
}

void OnError(AIKIT_HANDLE* handle, int32_t err, const char* desc){
    printf("OnError:%d\n",err);
}

// 初始化函数
int AEE_Init() {
    AIKIT_Configurator::builder()
        .app()
            .appID("7e80a282")
            .apiSecret("MDcwOTVlMzVlYzYzYzI2OTViYmFmOWFl")
            .apiKey("7adafe35f33937d6551db4021eeee3bc")
            .workDir("./")
        .auth()
            .authType(0)
        .log()
            .logLevel(LOG_LVL_INFO)
            .logPath("./");
    
    int ret = AIKIT_Init();
    if(ret != 0){
        printf("AIKIT_Init failed:%d\n",ret);
        return ret;
    }
    
    AIKIT_Callbacks cbs = {OnOutput, OnEvent, OnError};
    AIKIT_RegisterAbilityCallback(ABILITY, cbs);
    return 0;
}

// 文本转语音函数
int TestXTTS(const char* text, const char* output_file, int language, const char* role) {
    AIKIT_ParamBuilder* paramBuilder = nullptr;
    AIKIT_DataBuilder* dataBuilder = nullptr;   
    AIKIT_HANDLE* handle = nullptr;
    AiText* aiText_raw = nullptr;
    
    ttsFinished = false;

    paramBuilder = AIKIT_ParamBuilder::create();
    paramBuilder->clear();
    paramBuilder->param("vcn", role, strlen(role));  // 发音人角色 xiaoyan:xiaoyan(Female, Chinese), xiaofeng:xiaofeng(Male, Chinese), catherine:catherine(Female, US English)), chongchong:chongchong(Female, Chinese), john:john(Male, US English), christiance:德语, mariane:法语, anna:意大利语, xiaolin:日语, zhongcun:日语, kim:韩语, keshu:俄语, felisa:西班牙语, xiaofang:中文童声, xiaomei:粤语, xiaoyuan:中文普通话, abha:印地语, suparut:泰语, qianqian:中文qianqian, xiaoguan:中文xiaoguan
    paramBuilder->param("vcnModel", role, strlen(role));  
    paramBuilder->param("language", language);  //1:中文, 2:英文, 3:法语, 5:日语, 6:俄语, 9:德语, 15:意大利语, 16:韩语, 23:西班牙语, 12:粤语, 8:印地语, 27:泰语
    paramBuilder->param("pitch", 60);      // 语调  0~100
    paramBuilder->param("speed", 65);      // 语速  0~100
    paramBuilder->param("reg", 0);          // 英文发音 	0:引擎自动判断, 1:按字母发音, 2:按单词发音
    paramBuilder->param("rdn", 0);          // 使用参数   0:引擎自动判断, 1:按数字发音, 2:按字符串发音
    paramBuilder->param("textEncoding", "UTF-8", strlen("UTF-8"));
    
    int ret = AIKIT_Start(ABILITY, AIKIT_Builder::build(paramBuilder), nullptr, &handle);
    printf("AIKIT_Start:%d\n", ret);
    if(ret != 0){
        goto exit;
    }
    
    dataBuilder = AIKIT_DataBuilder::create();
    dataBuilder->clear();
    aiText_raw = AiText::get("text")->data(text, strlen(text))->once()->valid();
    dataBuilder->payload(aiText_raw);

    fin = fopen(output_file, "wb");
    if (fin == nullptr) {
        printf("fopen %s fail.\n", output_file);
        ret = -1;
        goto exit;
    }
    
    ret = AIKIT_Write(handle, AIKIT_Builder::build(dataBuilder));
    printf("AIKIT_Write:%d\n", ret);
    if(ret != 0){
        fclose(fin);
        goto exit;
    }

    // 等待转换完成
    while(ttsFinished != true){
        usleep(1000);
    }
    
    ret = AIKIT_End(handle);

exit:
    if (fin != nullptr) {
        fclose(fin);
        fin = nullptr;
    }   
    if(paramBuilder != nullptr){
        delete paramBuilder;
        paramBuilder = nullptr;
    }
    if(dataBuilder != nullptr){
        delete dataBuilder;
        dataBuilder = nullptr;
    }
    
    return ret;
}

void AEE_Cleanup() {
    AIKIT_UnInit();
}

4.编译上面的cpp为动态库:./build_interface.sh

#!/bin/bash
# build_interface.sh - 编译为动态库

g++ -std=c++11 -fPIC -shared -o libaikit.so aikit_interface.cpp -Iinclude -Llibs -laikit -Wl,-rpath=libs

echo "编译完成,生成 libaikit.so"

5.用python调用前,根据SDK的readme描述,需要先添加环境变量:

export LD_LIBRARY_PATH=./libs:$LD_LIBRARY_PATH

6.测试调用,使用ctypes库

# tts_simple.py - 简单的文本转语音测试

import ctypes
import os
from ctypes import c_char_p, c_int

class SimpleTTS:
    def __init__(self):
        # 获取当前脚本所在目录
        current_dir = os.path.dirname(os.path.abspath(__file__))
        lib_path = os.path.join(current_dir, 'libaikit.so')
        
        print(f"尝试加载库: {lib_path}")
        
        # 检查库文件是否存在
        if not os.path.exists(lib_path):
            print(f"错误: 库文件不存在: {lib_path}")
            print("当前目录文件列表:")
            for file in os.listdir(current_dir):
                print(f"  {file}")
            raise FileNotFoundError(f"找不到库文件: {lib_path}")
        
        try:
            self.lib = ctypes.CDLL(lib_path)
            print("动态库加载成功")
        except Exception as e:
            print(f"加载动态库失败: {e}")
            raise
        
        self._setup_functions()
        
        # 初始化SDK
        print("正在初始化TTS SDK...")
        result = self.lib.AEE_Init()
        if result != 0:
            raise RuntimeError(f"TTS SDK初始化失败,错误码: {result}")
        print("TTS SDK 初始化成功")
    
    def _setup_functions(self):
        self.lib.AEE_Init.argtypes = []
        self.lib.AEE_Init.restype = c_int
        self.lib.TestXTTS.argtypes = [c_char_p, c_char_p, c_int, c_char_p]
        self.lib.TestXTTS.restype = c_int
        self.lib.AEE_Cleanup.argtypes = []
        self.lib.AEE_Cleanup.restype = None
    
    def tts(self, text, output_file, language=1, voice="xiaofang"):
        """
        文本转语音
        
        Args:
            text: 要转换的文本
            output_file: 输出文件路径
            language: 1-中文, 2-英文
            voice: 发音人 (xiaofang, catherine等)
        """
        print(f"开始文本转语音...")
        print(f"文本: {text}")
        print(f"输出文件: {output_file}")
        print(f"语言: {language}")
        print(f"发音人: {voice}")
        
        result = self.lib.TestXTTS(
            text.encode('utf-8'),
            output_file.encode('utf-8'),
            language,
            voice.encode('utf-8')
        )
        
        success = (result == 0)
        if success:
            print(f"文本转语音成功,音频保存至: {output_file}")
        else:
            print(f"文本转语音失败,错误码: {result}")
        
        return success
    
    def __del__(self):
        if hasattr(self, 'lib'):
            print("清理资源...")
            self.lib.AEE_Cleanup()

# 使用示例
if __name__ == "__main__":
    try:
        tts = SimpleTTS()
        
        # 中文转换
        print("\n" + "="*50)
        success = tts.tts("你好,这是一个测试", "./test_chinese.pcm", 1, "xiaoyan")
        print(f"中文转换: {'成功' if success else '失败'}")
        
        
    except Exception as e:
        print(f"发生错误: {e}")

7.如果步骤6正常,即可将该服务伪装成openai接口格式,用于连接dify,步骤参考文章头部的链接,注:该SDK返回的pcm格式音频文件,而dify并不支持pcm格式音频输入,所以这里需要根据需要,修改cpp的输出,亦或是在python服务中将pcm转为mp3格式

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐