【C/C++】Windows SAPI自实现文字转语音

news/2025/2/2 7:44:17 标签: c语言, c++, windows

本文通过封装Windows SAPI(Speech Application Programming Interface),提供了一个现代化的C++接口实现文字转语音功能。主要特性包括支持同步/异步语音合成、可调节语速(-10到10)和音量控制(0-100%),同时支持将合成语音保存为WAV文件,并自动处理特殊字符转义,设计上也确保了线程安全。该接口依赖于Windows系统(需.NET Framework支持)、PowerShell 5.1及以上版本,以及C++11或更高版本。完整代码在文字末尾提供。

快速开始

基础使用示例

#include "tts.hpp"

int main() {
    TTS::TextToSpeech tts;
    
    // 设置语音参数
    tts.set_rate(5);    // 加快语速
    tts.set_volume(80); // 80%音量
    
    // 同步朗读
    tts.speak_sync("Hello, welcome to the text-to-speech system.");
    
    // 异步朗读
    auto future = tts.speak_async("This is an async operation.");
    future.wait(); // 等待完成
    
    // 保存到文件
    std::string filename = tts.save_to_wav("Audio saved to file.");
    return 0;
}

核心功能详解

语音参数设置

语速控制 (set_rate())

void set_rate(int rate);  // 范围:-10 ~ 10
  • 正值加快语速
  • 负值减慢语速
  • 自动钳制在有效范围内

音量控制 (set_volume())

void set_volume(int volume);  // 范围:0 ~ 100
  • 0表示静音
  • 100表示最大音量
  • 支持百分比精确控制

同步朗读 (speak_sync())

bool speak_sync(const std::string& text);
  • 阻塞当前线程直到朗读完成
  • 返回执行状态(true表示成功)
  • 适合需要顺序执行的场景

示例:

if (!tts.speak_sync("Critical system alert!")) {
    // 错误处理
}

异步朗读 (speak_async())

std::future<bool> speak_async(const std::string& text);
  • 立即返回std::future对象

  • 支持多种等待方式:

    auto future = tts.speak_async("Processing completed");
    
    // 方式1:阻塞等待
    future.wait();
    
    // 方式2:轮询检查
    while (future.wait_for(100ms) != std::future_status::ready) {
        // 执行其他任务
    }
    
    // 获取结果
    bool success = future.get();
    

保存音频文件 (save_to_wav())

std::string save_to_wav(const std::string& text, 
                       const std::string& filename = "");
  • 自动生成临时文件(当filename为空时)
  • 返回最终文件路径
  • 文件保存位置规则:
    • 指定filename:使用完整路径
    • 未指定:生成随机文件名(系统临时目录)

示例:

// 自动生成临时文件
auto auto_file = tts.save_to_wav("Automatic filename");

// 自定义路径
std::string custom_path = R"(C:\audio\alert.wav)";
auto custom_file = tts.save_to_wav("Custom path", custom_path);

高级用法

批量语音生成

std::vector<std::future<bool>> batch_process() {
    TTS::TextToSpeech tts;
    std::vector<std::future<bool>> results;
    
    for (int i = 0; i < 10; ++i) {
        std::string text = "Message " + std::to_string(i);
        results.push_back(tts.speak_async(text));
    }
    return results;
}

实时进度跟踪

void monitor_async() {
    auto future = tts.speak_async("Long running operation");
    
    std::thread monitor([&future]{
        while (future.wait_for(1s) != std::future_status::ready) {
            std::cout << "Synthesizing..." << std::endl;
        }
        std::cout << "Completed with status: " << future.get() << std::endl;
    });
    monitor.detach();
}

注意事项与最佳实践

字符处理

  • 自动转义XML特殊字符:&, <, >, ", '

  • 支持多语言文本(需系统语音包支持)

  • 建议预处理用户输入:

    std::string sanitize_input(const std::string& raw) {
        // 移除控制字符等
        std::string filtered;
        std::copy_if(raw.begin(), raw.end(), std::back_inserter(filtered),
            [](char c){ return std::isprint(c); });
        return filtered;
    }
    

性能优化

  • 复用TextToSpeech实例(避免重复初始化)

  • 异步操作时注意生命周期管理:

    // 错误示例(对象提前销毁):
    auto future = TTS::TextToSpeech().speak_async("text");
    
    // 正确做法:
    auto tts = std::make_shared<TTS::TextToSpeech>();
    auto future = tts->speak_async("text");
    

错误处理

  • 检查返回值:

    if (!tts.speak_sync("text")) {
        std::cerr << "Speech synthesis failed" << std::endl;
    }
    
  • 常见错误原因:

    • PowerShell访问权限不足
    • 无效的文件路径
    • 系统语音引擎故障

常见问题解答

Q:支持哪些音频格式?
A:目前仅支持WAV格式,由系统API决定

Q:如何处理中文字符?
A:需确保:

  1. 系统已安装中文语音包
  2. 代码文件使用UTF-8编码
  3. 控制台支持Unicode(建议使用chcp 65001)

Q:为什么需要生成批处理文件?
A:为了解决:

  • PowerShell直接执行的编码问题
  • 长命令行参数限制
  • 错误代码捕获需求

Q:最大支持文本长度?
A:由系统限制决定,建议分段处理超过1MB的文本

Q:如何实现语音中断?
A:当前版本未实现,但可以通过销毁对象终止异步操作

TTS.hpp 源代码

#pragma once
#include <string>
#include <sstream>
#include <cstdlib>
#include <random>
#include <atomic>
#include <thread>
#include <memory>
#include <system_error>
#include <future>
#include <fstream>
#include <cstdio>

#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

namespace TTS {

class TextToSpeech {
public:
    static constexpr int MIN_RATE = -10;
    static constexpr int MAX_RATE = 10;
    static constexpr int MIN_VOLUME = 0;
    static constexpr int MAX_VOLUME = 100;

    explicit TextToSpeech() = default;

    // 设置语音速率(-10~10)
    void set_rate(int rate) {
        rate_ = clamp(rate, MIN_RATE, MAX_RATE);
    }

    // 设置音量(0~100)
    void set_volume(int volume) {
        volume_ = clamp(volume, MIN_VOLUME, MAX_VOLUME);
    }

    // 同步朗读(阻塞直到完成)
    bool speak_sync(const std::string& text) {
        return execute_command(generate_ps_command(text));
    }

    // 异步朗读(立即返回)
    std::future<bool> speak_async(const std::string& text) {
        return std::async(std::launch::async, [this, text] { return this->speak_sync(text); });
    }

    // 生成临时WAV文件(返回文件路径)
    std::string save_to_wav(const std::string& text, const std::string& filename = "") {
        std::string full_path;
        bool clean_up;
        std::tie(full_path, clean_up) = generate_temp_path(filename, ".wav");

        std::string command = generate_ps_command(text, full_path);
        if (!execute_command(command)) {
            if (clean_up) std::remove(full_path.c_str());
            return "";
        }
        return full_path;
    }

private:
    int rate_ = 0; // 默认语速
    int volume_ = 100; // 默认音量
    std::atomic<bool> cancel_flag_{false};

    // 生成PowerShell命令
    std::string generate_ps_command(const std::string& text, const std::string& output_file = "") const {
        std::ostringstream oss;
        oss << "powershell -Command \"";
        oss << "Add-Type -AssemblyName System.Speech; ";
        oss << "$speech = New-Object System.Speech.Synthesis.SpeechSynthesizer; ";

        oss << "$speech.Rate = " << rate_ << "; ";
        oss << "$speech.Volume = " << volume_ << "; ";

        if (!output_file.empty()) {
            oss << "$speech.SetOutputToWaveFile('" << output_file << "'); ";
        } else {
            oss << "$speech.SetOutputToDefaultAudioDevice(); ";
        }

        oss << "$speech.Speak([System.Xml.XmlConvert]::VerifyXmlChars('"
            << escape_ps_string(escape_xml(text)) << "'));\"";

        return oss.str();
    }

    // 转义 PowerShell 字符串
    std::string escape_ps_string(const std::string& text) const {
        std::string result;
        result.reserve(text.size() * 2);
        for (char c : text) {
            result += (c == '\'') ? "''" : std::string(1, c);
        }
        return result;
    }

    // 执行命令并返回结果
    bool execute_command(const std::string& command) const {
        // 创建并写入批处理文件
        std::string bat_path;
        bool dummy;
        std::tie(bat_path, dummy) = generate_temp_path("tts_", ".bat");

        std::ofstream bat_file(bat_path);
        if (!bat_file) return false;

        bat_file << "@echo off\n"
                 << "chcp 65001 > nul\n"
                 << command << "\n"
                 << "exit /b %ERRORLEVEL%";
        bat_file.close();

        // 执行批处理文件
        std::string cmd = "cmd /c \"" + bat_path + "\"";
        int result = std::system(cmd.c_str());

        // 清理临时文件
        std::remove(bat_path.c_str());

        return (result == 0);
    }

    // 生成临时文件路径
    std::tuple<std::string, bool> generate_temp_path(const std::string& prefix = "tts_", const std::string& extension = "") const {
        static std::random_device rd;
        static std::mt19937 gen(rd());
        std::uniform_int_distribution<> dis(0, 15);

        std::string full_path;
        bool need_cleanup = false;

        if (prefix.empty()) {
            char tmp_name[L_tmpnam];
            if (std::tmpnam(tmp_name)) {
                full_path = tmp_name;
                need_cleanup = true;
            }
        } else {
            const std::string temp_dir = get_temp_directory();
            do {
                std::string unique_part;
                for (int i = 0; i < 8; ++i) {
                    unique_part += "0123456789abcdef"[dis(gen) % 16];
                }
                full_path = temp_dir + "\\" + prefix + unique_part + extension;
            } while (file_exists(full_path));
        }

        return {full_path, need_cleanup};
    }

    // XML 转义
    static std::string escape_xml(std::string data) {
        std::string buffer;
        buffer.reserve(data.size());
        for (char c : data) {
            switch (c) {
                case '&':  buffer += "&amp;";  break;
                case '\"': buffer += "&quot;"; break;
                case '\'': buffer += "&apos;"; break;
                case '<':  buffer += "&lt;";   break;
                case '>':  buffer += "&gt;";   break;
                default:   buffer += c;        break;
            }
        }
        return buffer;
    }

    // 范围限制函数
    template <typename T>
    static T clamp(T value, T min, T max) {
        return (value < min) ? min : (value > max) ? max : value;
    }

    // 获取临时目录
    static std::string get_temp_directory() {
        const char* tmp = std::getenv("TEMP");
        if (!tmp) tmp = std::getenv("TMP");
        return tmp ? tmp : ".";
    }

    // 检查文件是否存在
    static bool file_exists(const std::string& path) {
#ifdef _WIN32
        return ::_access(path.c_str(), 0) == 0;
#else
        return ::access(path.c_str(), F_OK) == 0;
#endif
    }
};

} // namespace TTS

http://www.niftyadmin.cn/n/5839927.html

相关文章

【力扣】15.三数之和

AC截图 题目 思路 这道题如果简单的用暴力三重遍历去做&#xff0c;会超时。所以我们思考假如有三个下标&#xff0c;i&#xff0c;l&#xff0c;r 其中i0&#xff08;初始&#xff09;&#xff0c;li1 rnums.size()-1 我们固定nums[i]的值&#xff0c;那么就转换为两数之和…

芯片AI深度实战:给vim装上AI

系列文章&#xff1a; 芯片AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 芯片AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 芯片AI深度实战&#xff1a;给vim装上AI-CSDN博客 芯片AI深度实战&#xff1a;火的编程AI&…

探索前端的未来:深度使用 SolidJS 构建高性能用户界面

SolidJS 是近年来前端开发领域的一颗新星&#xff0c;它结合了 React 的思维模式和 Vue 的性能优势&#xff0c;提供了一种高效且轻量的框架解决方案。本文将带你深入了解 SolidJS 的实际使用&#xff0c;并探索如何利用其核心特性构建现代化的高性能应用。 为什么选择 SolidJS…

第七章 交付绩效域

项目管理的除外责任&#xff1a;定义不做什么。项目范围的完成情况是根据项目管理计划来衡量的。工作分解结构的最底层被称为工作包。质量管理既关注过程又关注结果。 1. 交付绩效域的环节 确保项目做且只做所需的全部工作&#xff0c;且实现战略和价值定义和控制哪些工作包含…

实现智能教室能耗监测与管理系统的详细方案

以下是一个完整的实现智能教室能耗监测与管理系统的详细方案,涵盖深度学习模型研发、教室场景适应性分析、系统架构设计、前端展示、后端服务以及测试评估等方面,使用 Python 语言完成。 1. 深度学习模型研发 1.1 数据准备 首先,你需要收集大量的教室照片,并对其中的关键…

Maya软件安装步骤与百度网盘链接

软件简介&#xff1a; MAYA软件是Autodesk旗下的著名三维建模和动画软件。maya软件功能更为强大&#xff0c;体系更为完善&#xff0c;因此国内很多的三维动画制作人员都开始转向maya&#xff0c;maya软件已成为三维动画软件的主流。 百度网盘链接: https://pan.baidu.com/s…

【赵渝强老师】Spark RDD的依赖关系和任务阶段

Spark RDD彼此之间会存在一定的依赖关系。依赖关系有两种不同的类型&#xff1a;窄依赖和宽依赖。 窄依赖&#xff1a;如果父RDD的每一个分区最多只被一个子RDD的分区使用&#xff0c;这样的依赖关系就是窄依赖&#xff1b;宽依赖&#xff1a;如果父RDD的每一个分区被多个子RD…

mybatis辅助配置

驼峰映射 sql里面定义字段通常是使用下划线定义 比如dept_id 而我们的后端属性通常就是驼峰命名 deptId 所以这两匹配进行自动赋值就比较麻烦 可以使用 select dept_id as deptId 来解决&#xff08;起别名&#xff09; 也可以用mybatis的辅助配置解决 第三种就是推荐的在spr…