File size: 14,024 Bytes
ead033d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c025a8a
ead033d
c025a8a
ead033d
 
 
c025a8a
ead033d
c025a8a
ead033d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c025a8a
ead033d
c025a8a
ead033d
c025a8a
ead033d
c025a8a
ead033d
c025a8a
ead033d
 
 
 
 
 
 
c025a8a
 
ead033d
c025a8a
ead033d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6e864fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ead033d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <iomanip>
#include <filesystem>
#include "tts_engine.hpp"

void print_banner() {
    std::cout << "===========================================================================" << std::endl;
    std::cout << "🎙️  HỆ THỐNG ĐỌC TIẾNG NÓI C++ NATIVE (MATCHA-TTS + VOCOS ONNX RUNTIME)" << std::endl;
    std::cout << "===========================================================================" << std::endl;
}

void print_help(const char* prog_name) {
    std::cout << "Cách sử dụng:\n"
              << "  " << prog_name << " [Tùy chọn]\n\n"
              << "Các tùy chọn hỗ trợ:\n"
              << "  -t, --text <văn bản>    : Câu hoặc đoạn văn cần đọc\n"
              << "  -f, --file <đường dẫn>  : Đọc nội dung từ tệp văn bản (.txt)\n"
              << "  -o, --output <đường dẫn>: Đường dẫn tệp wav đầu ra (mặc định: outputs/output.wav)\n"
              << "  -s, --steps <số bước>   : Số bước giải vi phân ODE (1-10, mặc định: 2 cho Sway siêu tốc)\n"
              << "      --speed, -r <tỷ lệ> : Tốc độ đọc Google Sonic WSOLA (1.0 = chuẩn, 1.5, 2.0, 3.0, 4.0; mặc định: 1.0)\n"
              << "      --pitch <cao độ>    : Điều chỉnh cao độ bằng Google Sonic (0.5 - 2.0, mặc định: 1.0)\n"
              << "      --pause-config <tệp>: Cấu hình ngắt nghỉ thông minh theo dấu câu (.json, mặc định: pause_config.json)\n"
              << "      --pause <giây>      : Khoảng nghỉ cố định giữa các câu (ghi đè cấu hình dấu câu)\n"
              << "      --length-scale <tỷ lệ>: Co giãn âm vị AI thô (mặc định chuẩn: 1.0)\n"
              << "      --temp <nhiệt độ>   : Độ cảm xúc / pitch variation (mặc định: 0.9)\n"
              << "      --threads, -j <N>   : Số luồng CPU thực thi (1-N, mặc định: 4)\n"
              << "      --cpu / --gpu       : Chọn thiết bị tính toán (CPU hoặc GPU CUDA)\n"
              << "      --model-type <type> : Loại mô hình (int8, fp32, fp16; mặc định: int8)\n"
              << "      --no-norm           : Tắt bộ chuẩn hóa số & ngoại ngữ tự động\n"
              << "      --clean-only        : Chỉ in ra văn bản sau khi làm sạch (Clean Input) rồi thoát\n"
              << "  -h, --help              : Hiển thị hướng dẫn này\n"
              << std::endl;
}

int main(int argc, char* argv[]) {
    std::string text = "Chào mừng bạn đến với hệ thống đọc tiếng nói C++ Native siêu tốc!";
    std::string file_path = "";
    std::string output_path = "outputs/output.wav";
    std::string model_type = "int8"; // Cố định mặc định INT8 tối ưu AVX2/VNNI siêu tốc và ổn định
    std::string pause_config_file = "pause_config.json";
    int num_threads = 1; // Mặc định 1 core độc lập / request
    bool clean_only = false;
    
    TTSConfig config;
    config.n_timesteps = 2; // Chuẩn 2-step Sway Sampling INT8 tối ưu
    config.temperature = 0.9f;
    config.length_scale = 1.0f; // Cố định chuẩn phát âm tự nhiên
    config.pause_sec = -1.0f;   // Dưới 0 nghĩa là dùng cấu hình động theo dấu câu
    config.use_gpu = false;
    config.sonic_speed = 1.0f;  // Chuẩn tốc độ phát lại bằng Google Sonic WSOLA
    config.sonic_pitch = 1.0f;
    config.enable_normalization = true;

    // Phân tích đối số dòng lệnh
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if ((arg == "-t" || arg == "--text") && i + 1 < argc) {
            text = argv[++i];
        } else if ((arg == "-f" || arg == "--file") && i + 1 < argc) {
            file_path = argv[++i];
        } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) {
            output_path = argv[++i];
        } else if ((arg == "-s" || arg == "--steps") && i + 1 < argc) {
            config.n_timesteps = std::max(1, std::min(std::stoi(argv[++i]), 6));
        } else if ((arg == "--speed" || arg == "--tempo" || arg == "--sonic-speed" || arg == "-r") && i + 1 < argc) {
            config.sonic_speed = std::max(0.5f, std::min(std::stof(argv[++i]), 2.5f));
        } else if (arg == "--length-scale" && i + 1 < argc) {
            config.length_scale = std::max(0.5f, std::min(std::stof(argv[++i]), 2.0f));
        } else if ((arg == "--pitch" || arg == "--sonic-pitch") && i + 1 < argc) {
            config.sonic_pitch = std::max(0.7f, std::min(std::stof(argv[++i]), 1.5f));
        } else if (arg == "--temp" && i + 1 < argc) {
            config.temperature = std::max(0.6f, std::min(std::stof(argv[++i]), 1.2f));
        } else if (arg == "--pause" && i + 1 < argc) {
            config.pause_sec = std::stof(argv[++i]);
        } else if (arg == "--pause-config" && i + 1 < argc) {
            pause_config_file = argv[++i];
        } else if (arg == "--tail-words" && i + 1 < argc) {
            config.tail_words_prompt = std::stoi(argv[++i]);
        } else if (arg == "--model-type" && i + 1 < argc) {
            model_type = "int8"; // Cố định INT8
            i++;
        } else if ((arg == "--threads" || arg == "-j") && i + 1 < argc) {
            num_threads = std::max(1, std::min(std::stoi(argv[++i]), 16));
        } else if (arg == "--no-norm") {
            config.enable_normalization = false;
        } else if (arg == "--clean-only") {
            clean_only = true;
        } else if (arg == "--gpu") {
            config.use_gpu = true;
        } else if (arg == "--cpu") {
            config.use_gpu = false;
        } else if (arg == "-h" || arg == "--help") {
            print_help(argv[0]);
            return 0;
        }
    }

    print_banner();

    // Nếu chỉ định file, đọc nội dung file
    if (!file_path.empty()) {
        std::ifstream file(file_path);
        if (!file.is_open()) {
            std::cerr << "❌ Không thể mở tệp văn bản: " << file_path << std::endl;
            return 1;
        }
        std::stringstream buffer;
        buffer << file.rdbuf();
        text = buffer.str();
        std::cout << "📖 Đọc nội dung từ tệp: " << file_path << std::endl;
    }

    if (clean_only) {
        std::cout << "\n🧹 [CLEAN INPUT] KẾT QUẢ LÀM SẠCH VĂN BẢN ĐẦU VÀO:" << std::endl;
        std::cout << "---------------------------------------------------------------------------" << std::endl;
        std::string cleaned = matcha::TextNormalizer::clean_and_normalize(text);
        std::cout << cleaned << std::endl;
        std::cout << "---------------------------------------------------------------------------" << std::endl;
        return 0;
    }

    config.pause_config = matcha::TextNormalizer::load_pause_config(pause_config_file);

    std::cout << "📝 Nội dung văn bản  : \"" << (text.size() > 80 ? text.substr(0, 77) + "..." : text) << "\"" << std::endl;
    std::cout << "⚡ Số bước ODE       : " << config.n_timesteps << std::endl;
    std::cout << "⏩ Tốc độ đọc (speed): " << config.sonic_speed << "x (Google Sonic WSOLA)" << std::endl;
    if (config.sonic_pitch != 1.0f) {
        std::cout << "🎼 Cao độ (pitch)    : " << config.sonic_pitch << "x" << std::endl;
    }
    if (config.length_scale != 1.0f) {
        std::cout << "📏 AI length_scale   : " << config.length_scale << std::endl;
    }
    std::cout << "🎭 Temperature       : " << config.temperature << std::endl;
    std::cout << "🖥️  Thiết bị          : " << (config.use_gpu ? "GPU (CUDA)" : "CPU (" + std::to_string(num_threads) + " threads)") << std::endl;
    std::cout << "💾 File xuất         : " << output_path << std::endl;
    std::cout << "📦 Kiểu mô hình      : " << model_type << std::endl;
    std::cout << "🧹 Chuẩn hóa text    : " << (config.enable_normalization ? "Bật (Tự động chuyển số & ngoại ngữ)" : "Tắt") << std::endl;
    std::cout << "---------------------------------------------------------------------------" << std::endl;

    // Chọn tệp mô hình theo model_type (int8, fp32, fp16)
    std::string base_dir = "models_onnx/" + model_type;
    if (!std::filesystem::exists(base_dir)) {
        base_dir = "models_onnx/int8";
    }
    std::string enc_file = base_dir + "/matcha_encoder.onnx";
    std::string dec_file = base_dir + "/matcha_decoder.onnx";
    std::string vocos_file = base_dir + "/vocos.onnx";
    std::string symbols_file = "models_onnx/symbols.json";
    std::string prompt_enc_file = "models_onnx/prompt_encoder.onnx";

    if (config.n_timesteps <= 2 && !config.use_gpu) {
        config.use_sway = true;
        config.sway_coef = -1.0f;
    }

    TTSEngine engine;
    if (!engine.init(enc_file, dec_file, vocos_file, symbols_file, config.use_gpu, "models_onnx/prompt_encoder.onnx", num_threads)) {
        std::cerr << "❌ Khởi tạo TTSEngine thất bại!" << std::endl;
        return 1;
    }

    if (daemon_mode) {
        std::cout << "READY_DAEMON" << std::endl;
        std::string line;
        while (std::getline(std::cin, line)) {
            if (line == "EXIT" || line == "QUIT") break;
            if (line.empty()) continue;
            
            std::stringstream ss(line);
            std::string req_out, req_steps, req_speed, req_pitch, req_temp, req_norm, req_text_escaped;
            if (!std::getline(ss, req_out, '\t')) continue;
            if (!std::getline(ss, req_steps, '\t')) continue;
            if (!std::getline(ss, req_speed, '\t')) continue;
            if (!std::getline(ss, req_pitch, '\t')) continue;
            if (!std::getline(ss, req_temp, '\t')) continue;
            if (!std::getline(ss, req_norm, '\t')) continue;
            if (!std::getline(ss, req_text_escaped)) continue;

            // Unescape text
            std::string req_text;
            for (size_t i = 0; i < req_text_escaped.size(); ++i) {
                if (req_text_escaped[i] == '\\' && i + 1 < req_text_escaped.size()) {
                    if (req_text_escaped[i+1] == 'n') { req_text += '\n'; i++; }
                    else if (req_text_escaped[i+1] == '\\') { req_text += '\\'; i++; }
                    else req_text += req_text_escaped[i];
                } else {
                    req_text += req_text_escaped[i];
                }
            }

            TTSConfig req_cfg = config;
            try {
                req_cfg.n_timesteps = std::max(1, std::min(std::stoi(req_steps), 6));
                req_cfg.sonic_speed = std::max(0.5f, std::min(std::stof(req_speed), 2.5f));
                req_cfg.sonic_pitch = std::max(0.7f, std::min(std::stof(req_pitch), 1.5f));
                req_cfg.temperature = std::max(0.1f, std::min(std::stof(req_temp), 1.2f));
                req_cfg.enable_normalization = (req_norm == "1" || req_norm == "true");
            } catch (...) {}

            double dur_sec = 0.0;
            double proc_sec = 0.0;
            auto wav_data = engine.synthesize_paragraph(req_text, req_cfg, dur_sec, proc_sec);

            if (wav_data.empty()) {
                std::cout << "ERROR\tFailed to synthesize audio" << std::endl;
            } else {
                std::filesystem::path op(req_out);
                if (op.has_parent_path()) {
                    std::filesystem::create_directories(op.parent_path());
                }
                if (TTSEngine::save_wav(req_out, wav_data, 22050)) {
                    std::cout << "DONE\t" << std::fixed << std::setprecision(3) << dur_sec << "\t" << proc_sec << "\t" << req_out << std::endl;
                } else {
                    std::cout << "ERROR\tFailed to save wav file: " << req_out << std::endl;
                }
            }
        }
        return 0;
    }

    // Warmup tự động cho các tệp văn bản dài / chapter để kích hoạt bộ nhớ đệm CPU L3
    if (!file_path.empty()) {
        TTSConfig wup_cfg = config;
        wup_cfg.n_timesteps = 1;
        std::vector<float> wup_mel;
        int64_t wup_len = 0;
        engine.synthesize_sentence("Khởi động hệ thống.", wup_cfg, wup_mel, wup_len);
    }

    std::cout << "\n⏳ Đang tiến hành tổng hợp tiếng nói C++..." << std::endl;
    double duration_sec = 0.0;
    double process_time_sec = 0.0;

    auto wav = engine.synthesize_paragraph(text, config, duration_sec, process_time_sec);
    if (wav.empty()) {
        std::cerr << "❌ Không tạo được âm thanh!" << std::endl;
        return 1;
    }

    std::filesystem::path out_p(output_path);
    if (out_p.has_parent_path()) {
        std::filesystem::create_directories(out_p.parent_path());
    }

    if (!TTSEngine::save_wav(output_path, wav, 22050)) {
        std::cerr << "❌ Thất bại khi lưu file wav: " << output_path << std::endl;
        return 1;
    }

    std::cout << "---------------------------------------------------------------------------" << std::endl;
    std::cout << "✅ Hoàn tất tổng hợp âm thanh thành công!" << std::endl;
    std::cout << std::fixed << std::setprecision(2);
    std::cout << "⏱️  Thời lượng âm thanh : " << duration_sec << " giây" << std::endl;
    std::cout << std::setprecision(3);
    std::cout << "⚡ Thời gian suy luận  : " << process_time_sec << " giây" << std::endl;
    std::cout << std::setprecision(4);
    std::cout << "📊 Tỷ lệ RTF           : " << (process_time_sec / duration_sec) << std::endl;
    std::cout << std::setprecision(1);
    std::cout << "🚀 Tốc độ thực tế      : " << (duration_sec / process_time_sec) << "x Realtime" << std::endl;
    std::cout << "📂 File âm thanh đã lưu: " << output_path << std::endl;
    std::cout << "===========================================================================" << std::endl;

    return 0;
}