#include "TTSManager.h" #include #include #include TTSManager::TTSManager(QObject *parent) : QObject(parent), m_currentState(PlayState::Stopped) { // 初始化进程(用于执行 edge-tts + ffplay 命令) m_voiceProcess = new QProcess(this); connect(m_voiceProcess, QOverload::of(&QProcess::finished), this, &TTSManager::onPlayFinished); // 初始化任务调度定时器(单触发模式,避免任务重叠) m_taskTimer = new QTimer(this); m_taskTimer->setSingleShot(true); m_taskTimer->setInterval(200); // 任务切换间隔(避免进程占用) connect(m_taskTimer, &QTimer::timeout, this, &TTSManager::processNextTask); // 初始化临时音频路径(用户目录下,避免权限问题) m_tempAudioPath = QDir::homePath() + "/tts_temp_audio.wav"; // 检查环境是否就绪 if (!checkEnvironment()) { qCritical() << "[TTS] 环境检查失败!请按提示安装依赖"; m_currentState = PlayState::Failed; emit stateChanged(m_currentState); } } TTSManager::~TTSManager() { // 析构时停止所有任务并清理 stopAll(); delete m_voiceProcess; delete m_taskTimer; // 删除残留的临时音频文件 if (QFile::exists(m_tempAudioPath)) { QFile::remove(m_tempAudioPath); } } // 对外播放接口 PlayState TTSManager::speak(const QString &text, int repeatCount, int priority) { // 环境未就绪,直接返回失败 if (m_currentState == PlayState::Failed || !checkEnvironment()) { qWarning() << "[TTS] 播放失败:环境未就绪"; return PlayState::Failed; } // 文本为空,返回失败 QString trimmedText = text.trimmed(); if (trimmedText.isEmpty()) { qWarning() << "[TTS] 播放失败:文本为空"; return PlayState::Failed; } // 处理参数合法性(重复次数≥1,优先级0-3) int actualRepeat = qMax(1, repeatCount); int actualPriority = qBound(0, priority, 3); SpeechTask newTask = {trimmedText, actualRepeat, actualPriority, 0}; // 检查是否需要打断当前低优先级任务 if (handleHighPriorityTask(newTask)) { return m_currentState; } // 任务入队,若当前无播放则触发调度 enqueueTask(newTask); if (m_currentState == PlayState::Stopped || m_currentState == PlayState::Completed) { m_taskTimer->start(); } return m_currentState; } // 停止所有播放任务 void TTSManager::stopAll() { // 停止当前进程 if (m_voiceProcess->state() == QProcess::Running) { m_voiceProcess->kill(); m_voiceProcess->waitForFinished(500); // 等待进程退出 } // 清空任务队列和状态 m_taskQueue.clear(); m_currentTask = SpeechTask(); m_currentState = PlayState::Stopped; m_taskTimer->stop(); // 删除临时音频文件 if (QFile::exists(m_tempAudioPath)) { QFile::remove(m_tempAudioPath); } // 发送状态变化信号 emit stateChanged(m_currentState); qDebug() << "[TTS] 所有任务已停止"; } // 补充修改 onPlayFinished 函数,确保抢占后状态正确 void TTSManager::onPlayFinished(int exitCode, QProcess::ExitStatus exitStatus) { // 检查是否是被高优先级任务中断的情况 if (exitCode == 1 && m_currentState == PlayState::Playing) { return; } // 原有逻辑:清理临时文件 + 处理重复播放 + 调度下一个任务 if (QFile::exists(m_tempAudioPath)) { QFile::remove(m_tempAudioPath); } if (m_currentTask.currentRepeat < m_currentTask.repeatCount - 1) { m_currentTask.currentRepeat++; if (executeVoiceTask(m_currentTask.text)) { m_currentState = PlayState::Playing; emit stateChanged(m_currentState); return; } } m_currentState = PlayState::Completed; emit stateChanged(m_currentState); m_currentTask = SpeechTask(); m_taskTimer->start(); } // 处理下一个任务(从队列中取优先级最高的) void TTSManager::processNextTask() { // 队列空则切换到停止状态 if (m_taskQueue.isEmpty()) { m_currentState = PlayState::Stopped; emit stateChanged(m_currentState); return; } // 取出队列头部任务(已按优先级排序,头部优先级最高) m_currentTask = m_taskQueue.takeFirst(); m_currentTask.currentRepeat = 0; // 重置重复计数 // 执行任务,更新状态 if (executeVoiceTask(m_currentTask.text)) { m_currentState = PlayState::Playing; } else { m_currentState = PlayState::Failed; // 任务失败,调度下一个 m_taskTimer->start(); } emit stateChanged(m_currentState); emit currentTaskChanged(m_currentTask); } // 核心:执行 edge-tts 生成音频 + ffplay 播放 bool TTSManager::executeVoiceTask(const QString &text) { // 进程忙则返回失败 if (m_voiceProcess->state() == QProcess::Running) { qWarning() << "[TTS] 进程忙,无法执行新任务"; return false; } // 步骤1:处理文本和路径的双引号转义(先复制原字符串,再修改临时变量) QString escapedText = text; // 先复制原始文本 escapedText.replace("\"", "\\\""); // 仅修改临时变量 QString escapedAudioPath = m_tempAudioPath; // 先复制原始路径 escapedAudioPath.replace("\"", "\\\""); // 仅修改临时变量 // 步骤2:拼接命令(使用处理后的临时变量) QString cmd = QString( "source ~/tts_venv/bin/activate && " "edge-tts " "--voice zh-CN-XiaoxiaoNeural " "--text \"%1\" " "--write-media %2 && " "ffplay -autoexit -nodisp %2" ).arg(escapedText, escapedAudioPath); // 执行命令(通过 bash 解释器,支持 source 命令) m_voiceProcess->start("bash", QStringList() << "-c" << cmd); // 等待进程启动(超时1秒) if (!m_voiceProcess->waitForStarted(1000)) { qCritical() << "[TTS] 进程启动失败!请检查环境"; m_voiceProcess->kill(); return false; } return true; } bool TTSManager::checkEnvironment() { bool envReady = true; // 1. 修复 edge-tts 检测(不用 --version,改用 --help 验证是否存在) QProcess ttsCheck; QString ttsCheckCmd = QString( "source /home/zmj/tts_venv/bin/activate && edge-tts --help" // 用 --help 替代 --version ); ttsCheck.start("bash", QStringList() << "-c" << ttsCheckCmd); ttsCheck.waitForFinished(3000); // 检查退出码(0 表示存在,非0表示不存在) if (ttsCheck.exitCode() != 0) { qWarning() << "[TTS] 未检测到 edge-tts!请执行:source /home/zmj/tts_venv/bin/activate && pip3 install edge-tts"; envReady = false; } // 2. 修复 ffplay 检测(用 -version 单横线参数) QProcess ffplayCheck; ffplayCheck.start("/usr/bin/ffplay -version"); // 单横线 -version ffplayCheck.waitForFinished(2000); if (ffplayCheck.exitCode() != 0) { qWarning() << "[TTS] 未检测到 ffplay!请执行:sudo apt-get install ffmpeg"; envReady = false; } return envReady; } // 入队任务(按优先级降序排序,优先级高的在前) void TTSManager::enqueueTask(const SpeechTask &task) { // 插入队列并按优先级排序(3>2>1>0) m_taskQueue.append(task); std::sort(m_taskQueue.begin(), m_taskQueue.end(), [](const SpeechTask &a, const SpeechTask &b) { return a.priority > b.priority; }); } // 修改 handleHighPriorityTask 函数,强化抢占逻辑 bool TTSManager::handleHighPriorityTask(const SpeechTask &newTask) { // 1. 当前无任务或新任务优先级不高于当前,无需抢占 if (m_currentState != PlayState::Playing || newTask.priority <= m_currentTask.priority) { return false; } // 2. 高优先级任务需要抢占,记录当前任务状态以便后续恢复 SpeechTask interruptedTask = m_currentTask; // 计算剩余重复次数(当前已播次数 + 剩余次数) interruptedTask.currentRepeat = m_currentTask.currentRepeat; // 3. 强制停止当前播放进程(立即中断低优先级语音) if (m_voiceProcess->state() == QProcess::Running) { m_voiceProcess->kill(); // 强制终止进程 m_voiceProcess->waitForFinished(500); // 等待进程退出 } // 4. 被中断的任务重新入队(继续完成剩余播放次数) if (interruptedTask.currentRepeat < interruptedTask.repeatCount - 1) { // 更新剩余重复次数(减去已完成的1次) interruptedTask.repeatCount -= (interruptedTask.currentRepeat + 1); interruptedTask.currentRepeat = 0; enqueueTask(interruptedTask); } // 5. 立即执行高优先级任务 m_currentTask = newTask; m_currentTask.currentRepeat = 0; // 重置重复计数 if (executeVoiceTask(m_currentTask.text)) { m_currentState = PlayState::Playing; emit stateChanged(m_currentState); emit currentTaskChanged(m_currentTask); } else { m_currentState = PlayState::Failed; m_taskTimer->start(); // 若启动失败,调度下一个任务 } return true; }