单缓或先存再播.txt 10.9 KB
#include <fstream>
#include <assert.h>
#include <cstring>
#include <atomic>
#include <unistd.h>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <cstdio>   // 新增,用于文件操作

#include "JZsdkLib.h"
#include "version_choose.h"
#include "JZsdk_base/JZring/JZring.h"

#ifdef IFLAY_TTS_2_CONFIG_STATUS_ON

#include "aikit_biz_api.h"
#include "aikit_constant.h"
#include "aikit_biz_config.h"
#include "iflytek_tts.h"
#include "AudioDeal/AudioDeal.h"
#include "../../Megaphone.h"
#include "iflytek_tts.h"

using namespace std;
using namespace AIKIT;

static std::atomic_bool ttsFinished(false);
static std::atomic_bool g_playThreadRunning(false);
static const char *ABILITY = "e2e44feff";
static AIKIT_HANDLE *g_AikitHandle = nullptr; // 合成句柄

static int IflytekLib_2_StopTts();

#define IFLYTEK_RING_BUF_SIZE (1024 * 100)  // 100KB缓冲区
static T_JZringHandle g_ringHandle = nullptr;
static char *g_ringBuf = nullptr;
static int g_AudioPlayFinshFlag = JZ_FLAGCODE_OFF;

// 同步机制
static std::mutex g_mutex;
static std::condition_variable g_cv;

// 新增:文件模式相关变量
static std::atomic<bool> g_useFileMode(false);
static FILE* g_tempFile = nullptr;                // 临时文件指针
static std::mutex g_fileMutex;                    // 保护文件写入

// 播放线程函数(保持不变,仅在实时模式下有数据可读)
void PlaybackThreadFunc()
{
    U8_t buffer[2048]; // 每次读取2KB
    U32_t readSize = 0;
    T_JZsdkReturnCode ret;
    
    g_playThreadRunning = true;

    while (g_playThreadRunning) 
    {
        // 等待有数据可读
        {
            std::unique_lock<std::mutex> lock(g_mutex);
            U32_t dataCount = 0;
            JZring_GetDataCount(g_ringHandle, &dataCount);

            //JZSDK_LOG_DEBUG("缓冲区数据量: %d", dataCount);
            
            if (dataCount == 0) {
                // 等待最多100ms或通知
                g_cv.wait_for(lock, std::chrono::milliseconds(100));
                continue;
            }
        }

        g_AudioPlayFinshFlag = JZ_FLAGCODE_ON;
        
        // 从环形缓冲区读取数据
        ret = JZring_Read(g_ringHandle, buffer, sizeof(buffer), &readSize);
        if (ret != JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS || readSize == 0) {
            usleep(10000); // 10ms
            continue;
        }
        
        // 发送到音频播放器
        AudioDeal_PcmDataInput_TextSteam(16000, buffer, readSize);

        g_AudioPlayFinshFlag = JZ_FLAGCODE_OFF;
    }
    
    g_playThreadRunning = false;
}

// 新增:播放临时文件内容
static void PlayTempFile()
{
    if (!g_tempFile) return;

    fseek(g_tempFile, 0, SEEK_SET);  // 重置文件指针到开头
    U8_t buffer[2048];
    size_t bytesRead;

    while ((bytesRead = fread(buffer, 1, sizeof(buffer), g_tempFile)) > 0) {
        AudioDeal_PcmDataInput_TextSteam(16000, buffer, bytesRead);
        // 检查是否被外部停止
        if (Megaphone_MegDataGenFlag(JZ_FLAGCODE_GET, 0) == JZ_FLAGCODE_OFF) {
            break;
        }
    }

    // 关闭并删除临时文件(tmpfile 关闭即删除)
    fclose(g_tempFile);
    g_tempFile = nullptr;
}

void OnOutput(AIKIT_HANDLE* handle, const AIKIT_OutputData* output) 
{
    // 检测数据生成标志位是否有关闭
    if (Megaphone_MegDataGenFlag(JZ_FLAGCODE_GET, 0) == JZ_FLAGCODE_OFF) {
        IflytekLib_2_StopTts();
        return;
    }
    
    if (!output->node->value) return;

    if (g_useFileMode) {
        // 文件模式:将数据写入临时文件
        std::lock_guard<std::mutex> lock(g_fileMutex);
        if (g_tempFile) {
            size_t written = fwrite(output->node->value, 1, output->node->len, g_tempFile);
            if (written != static_cast<size_t>(output->node->len)) {
                JZSDK_LOG_ERROR("写入临时文件失败");
            }
        }
    } else {
        // 实时模式:写入环形缓冲区(原有逻辑)
        T_JZsdkReturnCode ret;
        U32_t written = 0;
        
        while (written < static_cast<U32_t>(output->node->len)) {
            U32_t chunkSize = std::min(static_cast<U32_t>(output->node->len) - written, 
                                      static_cast<U32_t>(4096));
            
            ret = JZring_Write(g_ringHandle, 
                              reinterpret_cast<U8_t*>(output->node->value) + written, 
                              chunkSize);
            if (ret == JZ_ERROR_SYSTEM_MODULE_CODE_BUFFER_SIZE_NOT_ENOUGH)
            {
                //缓冲区暂无空间,等待一段时间再写入
                usleep(10000); // 10ms
                continue;
            }    
            
            if (ret != JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS && ret != JZ_ERROR_SYSTEM_MODULE_CODE_BUFFER_SIZE_NOT_ENOUGH) 
            {
                JZSDK_LOG_ERROR("写入环形缓冲区失败: %d", ret);
                break;
            }
            
            written += chunkSize;
        }
        
        // 通知播放线程有新数据
        g_cv.notify_one();
    }
}

void OnEvent(AIKIT_HANDLE* handle, AIKIT_EVENT eventType, const AIKIT_OutputEvent* eventValue) {
    if (eventType == AIKIT_Event_End) {
        ttsFinished = true;
        JZSDK_LOG_INFO("合成完成");
        
        // 通知播放线程所有数据已生成(实时模式有用)
        g_cv.notify_one();
    }
}

void OnError(AIKIT_HANDLE* handle, int32_t err, const char* desc) {
    JZSDK_LOG_ERROR("TTS错误: %d, %s", err, desc);
    IflytekLib_2_StopTts();
}

// 修改:增加 useFileMode 参数,默认 false(保持原有行为)
int IflytekLib_2_TextToTts(int language, 
                           const char *TtsRole, 
                           const char *text, 
                           int speed, 
                           int volume,
                           bool useFileMode = false)   // 新增参数
{
    AIKIT_ParamBuilder* paramBuilder = nullptr;
    AIKIT_DataBuilder* dataBuilder = nullptr;   
    AiText* aiText_raw = nullptr;
    int ret = 0;

    // 设置模式
    g_useFileMode = useFileMode;

    // 重置完成标志
    ttsFinished = false;

    if (g_useFileMode) {
        // 文件模式:创建临时文件
        std::lock_guard<std::mutex> lock(g_fileMutex);
        g_tempFile = tmpfile();  // 系统临时文件,关闭后自动删除
        if (!g_tempFile) {
            JZSDK_LOG_ERROR("创建临时文件失败,回退到实时模式");
            g_useFileMode = false;
        }
    } else {
        // 实时模式:重置环形缓冲区
        JZring_Reset(g_ringHandle);
    }

    paramBuilder = AIKIT_ParamBuilder::create();
    paramBuilder->clear();
    // 设置发音人
    paramBuilder->param("vcn", TtsRole, strlen(TtsRole));
    paramBuilder->param("vcnModel", TtsRole, strlen(TtsRole));
    // 设置语种
    paramBuilder->param("language", language);
    // 设置文本编码
    paramBuilder->param("textEncoding", "UTF-8", strlen("UTF-8"));
    // 音量
    paramBuilder->param("volume", volume);
    // 语速
    paramBuilder->param("speed", speed);

    JZSDK_LOG_DEBUG("TTS Role:%s, Text:%s, language:%d", TtsRole, text, language);
    
    ret = AIKIT_Start(ABILITY, AIKIT_Builder::build(paramBuilder), nullptr, &g_AikitHandle);
    if(ret != 0) {
        JZSDK_LOG_ERROR("AIKIT_Start failed: %d", ret);
        goto exit;
    }

    dataBuilder = AIKIT_DataBuilder::create();
    dataBuilder->clear();
    aiText_raw = AiText::get("text")->data(text, strlen(text))->once()->valid();
    dataBuilder->payload(aiText_raw);

    ret = AIKIT_Write(g_AikitHandle, AIKIT_Builder::build(dataBuilder));
    if(ret != 0) {
        JZSDK_LOG_ERROR("AIKIT_Write failed: %d", ret);
        goto exit;
    }

    // 等待合成完成
    if (g_useFileMode) {
        // 文件模式:只需等待合成结束,无需等待播放
        while (!ttsFinished) {
            if (Megaphone_MegDataGenFlag(JZ_FLAGCODE_GET, 0) == JZ_FLAGCODE_OFF) {
                break;
            }
            usleep(10000);
        }
    } else {
        // 实时模式:等待合成完成且播放结束(原有逻辑)
        while (!ttsFinished || g_AudioPlayFinshFlag == JZ_FLAGCODE_ON) {
            if (Megaphone_MegDataGenFlag(JZ_FLAGCODE_GET, 0) == JZ_FLAGCODE_OFF) {
                break;
            }
            usleep(10000);
        }
    }

    JZSDK_LOG_INFO("合成结束");

    // 结束合成
    if (g_AikitHandle) {
        AIKIT_End(g_AikitHandle);
        g_AikitHandle = nullptr;
    }

    // 文件模式:合成完成后播放临时文件
    if (g_useFileMode && g_tempFile) {
        JZSDK_LOG_INFO("开始播放临时文件");
        PlayTempFile();   // 播放完毕后会自动关闭并删除文件
    }

exit:
    if(paramBuilder != nullptr) {
        delete paramBuilder;
    }
    if(dataBuilder != nullptr) {
        delete dataBuilder;
    }
    
    // 重置模式标志
    g_useFileMode = false;
    return ret;
}

int IflytekLib_2_Init()
{
    // 初始化环形缓冲区
    g_ringBuf = new char[IFLYTEK_RING_BUF_SIZE];
    if (JZring_Init(&g_ringHandle, reinterpret_cast<U8_t*>(g_ringBuf), IFLYTEK_RING_BUF_SIZE) 
        != JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
        JZSDK_LOG_ERROR("环形缓冲区初始化失败");
        return -1;
    }

    // 启动播放线程(实时模式下工作,文件模式下空转)
    std::thread(PlaybackThreadFunc).detach();

    // 初始化TTS SDK
    AIKIT_Configurator::builder()
        .app()
            .appID("03857dfd")
            .apiSecret("OTA2OTEzMTVlOGYwMjllMmJkYzEwZGY5")
            .apiKey("2b2c60f8a80b8cdfe45ae1058a25149a")
            .workDir("/root/Iflytek_2")
        .auth()
            .authType(0)
        .log()
            .logLevel(LOG_LVL_OFF)      //关闭日志打印
            .logMode(LOG_STDOUT);        //日志输出为控制台        
            
    int ret = AIKIT_Init();
    if(ret != 0) {
        JZSDK_LOG_ERROR("AIKIT_Init failed: %d", ret);
        return -1;
    }
    
    AIKIT_Callbacks cbs = {OnOutput, OnEvent, OnError};
    AIKIT_RegisterAbilityCallback(ABILITY, cbs);

    return 0;
}

int IflytekLib_2_UnInit()
{
    // 停止播放线程
    g_playThreadRunning = false;
    g_cv.notify_one();
    
    // 等待播放线程退出
    int waitCount = 0;
    while (g_playThreadRunning && waitCount++ < 50) {
        usleep(100000); // 100ms
    }
    
    // 反初始化TTS SDK
    if (g_AikitHandle) {
        AIKIT_End(g_AikitHandle);
        g_AikitHandle = nullptr;
    }
    
    AIKIT_UnInit();
    
    // 释放环形缓冲区
    if (g_ringHandle) {
        JZring_DeInit(g_ringHandle);
        g_ringHandle = nullptr;
    }
    
    if (g_ringBuf) {
        delete[] g_ringBuf;
        g_ringBuf = nullptr;
    }

    // 如果还有临时文件未关闭,关闭它(极少数情况)
    if (g_tempFile) {
        fclose(g_tempFile);
        g_tempFile = nullptr;
    }
    
    return 0;
}

static int IflytekLib_2_StopTts()
{
    if (g_AikitHandle) {
        AIKIT_End(g_AikitHandle);
        g_AikitHandle = nullptr;
        ttsFinished = true;
        JZSDK_LOG_INFO("TTS合成已停止");
    }
    
    // 通知播放线程
    g_cv.notify_one();
    return 0;
}

#endif