0%

语音识别_SAPI实现语音识别(四)

一、SAPI

SAPI (Speech Application Programming Interface) 进行语音识别过程笔记

SAPI 是微软提供的一个接口,用于语音识别和语音合成。以下是使用 SAPI 进行语音识别的过程笔记,包括初始化、配置、启动识别、处理结果等步骤。

1. 初始化 COM 库

在使用 SAPI 之前,必须初始化 COM 库。

1
2
3
4
HRESULT hr = ::CoInitialize(NULL);
if (FAILED(hr)) {
// 处理初始化失败
}

2. 创建语音识别引擎

创建语音识别引擎的实例。

1
2
3
4
5
CComPtr<ISpRecognizer> pRecognizer;
hr = CoCreateInstance(CLSID_SpInprocRecognizer, NULL, CLSCTX_ALL, IID_ISpRecognizer, (void**)&pRecognizer);
if (FAILED(hr)) {
// 处理创建失败
}

3. 配置语音识别引擎

设置语音识别引擎的属性,如音频输入设备和语音识别引擎。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CComPtr<ISpRecoContext> pRecoContext;
hr = pRecognizer->CreateRecoContext(&pRecoContext);
if (FAILED(hr)) {
// 处理创建失败
}

CComPtr<ISpRecoGrammar> pRecoGrammar;
hr = pRecoContext->CreateGrammar(0, &pRecoGrammar);
if (FAILED(hr)) {
// 处理创建失败
}

// 加载语法
hr = pRecoGrammar->LoadDictation(NULL, SPLO_STATIC);
if (FAILED(hr)) {
// 处理加载语法失败
}

4. 设置音频输入

选择和配置音频输入设备。

1
2
3
4
5
6
7
8
9
10
CComPtr<ISpAudio> pAudio;
hr = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &pAudio);
if (FAILED(hr)) {
// 处理创建失败
}

hr = pRecognizer->SetInput(pAudio, TRUE);
if (FAILED(hr)) {
// 处理设置输入失败
}

5. 启动识别

设置语音识别模式和开始识别过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hr = pRecoContext->SetNotifyWin32Event();
if (FAILED(hr)) {
// 处理设置失败
}

hr = pRecoGrammar->SetRuleState(NULL, NULL, SPRS_ACTIVE);
if (FAILED(hr)) {
// 处理设置规则状态失败
}

hr = pRecognizer->SetRecoState(SPRST_ACTIVE);
if (FAILED(hr)) {
// 处理设置识别状态失败
}

6. 处理识别结果

接收和处理识别结果。

1
2
3
4
5
6
7
8
9
10
CComPtr<ISpRecoResult> pResult;
hr = pRecoContext->GetResult(&pResult);
if (SUCCEEDED(hr)) {
CComBSTR bstrText;
hr = pResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &bstrText, NULL);
if (SUCCEEDED(hr)) {
std::wstring recognizedText(bstrText);
// 处理识别结果
}
}

7. 结束识别

停止识别并释放资源。

1
2
3
4
5
6
7
hr = pRecognizer->SetRecoState(SPRST_INACTIVE);
if (FAILED(hr)) {
// 处理设置失败
}

// 释放资源
::CoUninitialize();

总结

  1. 初始化 COM 库: 确保在任何 SAPI 操作之前正确初始化 COM 库,并在操作完成后释放资源。
  2. 创建和配置识别引擎: 创建语音识别引擎实例,并配置音频输入和语法。
  3. 启动和管理识别过程: 启动语音识别并处理识别结果。
  4. 资源管理: 确保在完成操作后正确释放资源。

这个过程为使用 SAPI 进行语音识别提供了一个基本的框架,你可以根据具体的需求进行扩展和调整。

二、数据库内容

识别数据库内容 回复数据库内容
序号 内容 序号 内容
0 小度小度 0 我在
1 调车白灯-8 9 1 调车白灯
2 负载断开 8 2 负载断开
3 进路开通 8 9 3 进路开通
4 母线重联、负载断开 4 母线重联、负载断开
5 注意进站 5 注意进站
6 开左侧门 6 开左侧门
7 欢迎您乘坐5号线列车 7 欢迎您乘坐5号线列车
8 列车运行前方是北京站,请下车的乘客做好准备 8 列车运行前方是北京站,请下车的乘客做好准备
9 列车运行前方是本次列车的终点站北京站,请您做好准备。感谢您乘坐本次列车,再见 9 列车运行前方是本次列车的终点站北京站,请您做好准备。感谢您乘坐本次列车,再见
10 各位乘客,为保证正点运营,请您不要挤靠车门,以免影响正常发车,谢谢合作 10 各位乘客,为保证正点运营,请您不要挤靠车门,以免影响正常发车,谢谢合作
11 各位乘客您好,列车现在关门,上不去车的乘客,请您等候下次列车,谢谢您的支持与合作 11 各位乘客您好,列车现在关门,上不去车的乘客,请您等候下次列车,谢谢您的支持与合作
12 各位乘客您好,现在是高峰时间,乘客较多,请您配合我们抓紧时间上、下车,谢谢您的合作 12 各位乘客您好,现在是高峰时间,乘客较多,请您配合我们抓紧时间上、下车,谢谢您的合作
13 各位乘客您好,列车自动广播系统发生故障,给您带来的不便,敬请谅解 13 各位乘客您好,列车自动广播系统发生故障,给您带来的不便,敬请谅解

三、数据库实现

1、启动mysql

用户密码:还是我的常用密码

1
2
3
4
5
6
7
8
9
10
PS D:\ASR\pocketsphinx> mysql --version
D:\DevelopmentTools\Mysql\mysql-5.7.24-winx64\bin\mysql.exe Ver 14.14 Distrib 5.7.24, for Win64 (x86_64)

PS D:\ASR\pocketsphinx> net start mysql
请求的服务已经启动。

请键入 NET HELPMSG 2182 以获得更多的帮助。

PS D:\ASR\pocketsphinx> mysql -u root -p
Enter password: **************

2、MySQL 数据库配置与插入中文数据

在 MySQL 中,插入中文数据需要确保数据库和表的字符集设置为支持中文的字符集(如 utf8mb4)。以下是创建数据库和表结构以及插入中文数据的正确方法。

1. 创建数据库和表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 创建数据库并设置字符集
CREATE DATABASE VoiceAssistant CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 使用创建的数据库
USE VoiceAssistant;

-- 创建识别内容表
CREATE TABLE RecognizeContent (
id INT AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL
);

-- 创建回复内容表
CREATE TABLE ReplyContent (
id INT AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL
);

2. 插入数据

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
-- 插入识别内容
INSERT INTO RecognizeContent (content) VALUES
('小度小度'),
('调车白灯'),
('负载断开'),
('进路开通'),
('母线重联、负载断开'),
('注意进站'),
('开左侧门'),
('欢迎您乘坐5号线列车'),
('列车运行前方是北京站,请下车的乘客做好准备'),
('列车运行前方是本次列车的终点站北京站,请您做好准备。感谢您乘坐本次列车,再见'),
('各位乘客,为保证正点运营,请您不要挤靠车门,以免影响正常发车,谢谢合作'),
('各位乘客您好,列车现在关门,上不去车的乘客,请您等候下次列车,谢谢您的支持与合作'),
('各位乘客您好,现在是高峰时间,乘客较多,请您配合我们抓紧时间上、下车,谢谢您的合作'),
('各位乘客您好,列车自动广播系统发生故障,给您带来的不便,敬请谅解');

-- 插入回复内容
INSERT INTO ReplyContent (content) VALUES
('我在'),
('调车白灯'),
('负载断开'),
('进路开通'),
('母线重联、负载断开'),
('注意进站'),
('开左侧门'),
('欢迎您乘坐5号线列车'),
('列车运行前方是北京站,请下车的乘客做好准备'),
('列车运行前方是本次列车的终点站北京站,请您做好准备。感谢您乘坐本次列车,再见'),
('各位乘客,为保证正点运营,请您不要挤靠车门,以免影响正常发车,谢谢合作'),
('各位乘客您好,列车现在关门,上不去车的乘客,请您等候下次列车,谢谢您的支持与合作'),
('各位乘客您好,现在是高峰时间,乘客较多,请您配合我们抓紧时间上、下车,谢谢您的合作'),
('各位乘客您好,列车自动广播系统发生故障,给您带来的不便,敬请谅解');

四、初始化SAPI和释放资源

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
void CSapiASRDlg::InitializeSAPI()
{
HRESULT hr = ::CoInitialize(NULL);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to initialize COM library."));
return;
}

hr = m_pRecognizer.CoCreateInstance(CLSID_SpInprocRecognizer);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create recognizer: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecognizer->CreateRecoContext(&m_pRecoContext);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create recognition context: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecoContext->CreateGrammar(1, &m_pGrammar);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create grammar: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pGrammar->LoadDictation(NULL, SPLO_STATIC);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to load dictation grammar: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pGrammar->SetDictationState(SPRS_INACTIVE);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set dictation state: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecoContext->SetNotifyWin32Event();
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set notify event: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION));
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set interest for recognition events: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = CoCreateInstance(CLSID_SpMMAudioIn, NULL, CLSCTX_INPROC_SERVER, IID_ISpAudio, (void**)&m_cpAudio);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create audio input object: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecognizer->SetInput(m_cpAudio, TRUE);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set audio input: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

AfxMessageBox(_T("Initialize SAPI is successful"));
}

CSapiASRDlg::~CSapiASRDlg()
{
if (m_bRunning)
{
m_bRunning = false;
}
if (m_pGrammar)
{
m_pGrammar->SetDictationState(SPRS_INACTIVE);
}
if (m_pRecoContext)
{
m_pRecoContext.Release();
}
if (m_pRecognizer)
{
m_pRecognizer.Release();
}
::CoUninitialize();
}

五、开始录音和结束录音

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
void CSapiASRDlg::OnBnClickedButtonStart()
{
HRESULT hr = m_pGrammar->SetDictationState(SPRS_ACTIVE);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to start recognition."));
return;
}

m_bRunning = true;
AfxMessageBox(_T("Recognition started."));
}

void CSapiASRDlg::OnBnClickedButtonStop()
{
if (!m_bRunning)
{
AfxMessageBox(_T("Recognition is not started."));
return;
}

HRESULT hr = m_pGrammar->SetDictationState(SPRS_INACTIVE);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to stop recognition."));
return;
}

m_bRunning = false;

// 在这里进行语音识别,并将结果显示在编辑框中
PerformRecognition();

AfxMessageBox(_T("Recognition stopped."));
}

六、识别语音

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
void CSapiASRDlg::PerformRecognition()
{
HRESULT hr = m_pRecoContext->WaitForNotifyEvent(1000); // 1-second timeout
if (FAILED(hr))
{
return;
}

CSpEvent event;
while (event.GetFrom(m_pRecoContext) == S_OK)
{
if (event.eEventId == SPEI_RECOGNITION)
{
ISpRecoResult* pResult = event.RecoResult();
if (pResult)
{
LPWSTR pwszText = nullptr;
hr = pResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, FALSE, &pwszText, NULL);
if (SUCCEEDED(hr))
{
CString* pNewText = new CString(pwszText);
PostMessage(WM_USER_UPDATE_TEXT, reinterpret_cast<WPARAM>(pNewText), 0);
CoTaskMemFree(pwszText);
}
}
}
}
}

BEGIN_MESSAGE_MAP(CSapiASRDlg, CDialogEx)
ON_MESSAGE(WM_USER_UPDATE_TEXT, &CSapiASRDlg::OnUpdateText)
ON_BN_CLICKED(IDC_BUTTON_CLEAR, &CSapiASRDlg::OnBnClickedButtonClear)
END_MESSAGE_MAP()

七、更新文本

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
LRESULT CSapiASRDlg::OnUpdateText(WPARAM wParam, LPARAM lParam)
{
CString* pNewText = reinterpret_cast<CString*>(wParam);
if (pNewText)
{
m_edtText.SetWindowText(*pNewText);
}

std::wstring newText((*pNewText).GetString());
std::wstring bestMatch;
int bestMatchId = -1;
int minDistance = INT_MAX;

int i = 1;
for (const auto& row_data : m_database)
{
int distance = levenshteinDistance(newText, std::wstring(row_data.GetString()));
if (distance < minDistance)
{
minDistance = distance;
bestMatch = row_data.GetString();
bestMatchId = i;
}
i++;
}

// 将最相似的结果输出到编辑框
m_edtResult.SetWindowText(bestMatch.c_str());
//CString content = GetReplyContent(bestMatch.c_str());
CString content = GetReplyContentById(bestMatchId);
m_edtResponse.SetWindowTextW(content);

delete pNewText;

return 0;
}

八、连接MYSQL数据库,获取对照文本

Visual Studio 2019连接MySQL数据库详细教程_visual studio服务器资源管理器 数据连接-CSDN博客

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
void CSapiASRDlg::connectToDatabase()
{
MYSQL* conn;
MYSQL_RES* res;
MYSQL_ROW row;

const char* server = "localhost";
const char* user = "root";
const char* password = "13525681378.Ll"; // 请替换为您的 MySQL 密码
const char* database = "VoiceAssistant";

conn = mysql_init(NULL);

// 连接数据库
if (mysql_real_connect(conn, server, user, password, database, 0, NULL, 0) == NULL)
{
AfxMessageBox(_T("mysql_real_connect() failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("mysql_real_connect() success"));
}

// 设置连接的字符集
if (mysql_set_character_set(conn, "utf8mb4"))
{
AfxMessageBox(_T("mysql_set_character_set() failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("mysql is utf8mb4"));
}

// 执行查询
if (mysql_query(conn, "SELECT * FROM RecognizeContent"))
{
AfxMessageBox(_T("SELECT * FROM RecognizeContent failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("SELECT * FROM RecognizeContent success"));
}

res = mysql_store_result(conn);

if (res == NULL)
{
AfxMessageBox(_T("mysql_store_result() failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("mysql_store_result() success"));
}

// 输出查询结果
int num_fields = mysql_num_fields(res);
while ((row = mysql_fetch_row(res)))
{
CString m_data;
for (int i = 0; i < num_fields; i++)
{
if (row[i] == NULL)
{
m_data.Format(_T("NULL"));
continue;
}

// 将 UTF-8 编码的字符串转换为宽字符字符串
int len = MultiByteToWideChar(CP_UTF8, 0, row[i], -1, NULL, 0);
if (len == 0)
{
CString errorMsg;
errorMsg.Format(_T("MultiByteToWideChar failed with error code: %d"), GetLastError());
AfxMessageBox(errorMsg);
continue;
}

std::wstring wstr(len, L'\0');
MultiByteToWideChar(CP_UTF8, 0, row[i], -1, &wstr[0], len);

// 去除最后一个空字符
if (!wstr.empty() && wstr.back() == L'\0') {
wstr.pop_back();
}

m_data.Format(_T("%s"), wstr.c_str());
}
m_database.push_back(m_data);
}

// 释放结果集
mysql_free_result(res);

// 关闭连接
mysql_close(conn);

// 显示查询结果
for (const auto& row : m_database)
{
AfxMessageBox(row);
}
}

报错:

1、找不到mysql.h

解决方案:将mysql的include路径和lib路径设置在项目属性中

1
2
3
4
5
D:\DevelopmentTools\Mysql\mysql-5.7.24-winx64\include

D:\DevelopmentTools\Mysql\mysql-5.7.24-winx64\lib

链接器找到libmysql.lib,添加进去即可

2、mysql查找的数据是?乱码

1
2
3
4
5
6
7
8
if (mysql_set_character_set(conn, "utf8mb4"))	// 设置连接的字符集为utf8mb4
{
AfxMessageBox(_T("mysql_set_character_set() failed"));
}
else
{
AfxMessageBox(_T("mysql is utf8mb4"));
}

同时,对得到的字符还要转换为宽字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将 UTF-8 编码的字符串转换为宽字符字符串
int len = MultiByteToWideChar(CP_UTF8, 0, row[i], -1, NULL, 0);
if (len == 0)
{
CString errorMsg;
errorMsg.Format(_T("MultiByteToWideChar failed with error code: %d"), GetLastError());
AfxMessageBox(errorMsg);
continue;
}

std::wstring wstr(len, L'\0');
MultiByteToWideChar(CP_UTF8, 0, row[i], -1, &wstr[0], len);
CString result(wstr.c_str());
AfxMessageBox(result);

九、语音识别结果处理

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
CString CSapiASRDlg::GetReplyContentById(int testId)
{
MYSQL* conn;
MYSQL_RES* res;
MYSQL_ROW row;
CString replyContent;

const char* server = "localhost";
const char* user = "root";
const char* password = "13525681378.Ll"; // 请替换为您的 MySQL 密码
const char* database = "VoiceAssistant";

conn = mysql_init(NULL);

if (mysql_real_connect(conn, server, user, password, database, 0, NULL, 0) == NULL)
{
AfxMessageBox(_T("mysql_real_connect() failed"));
mysql_close(conn);
return replyContent;
}

if (mysql_set_character_set(conn, "utf8mb4"))
{
AfxMessageBox(_T("mysql_set_character_set() failed"));
}


// 构建查询语句

std::string query = "SELECT content FROM ReplyContent WHERE id = " + std::to_string(testId);
AfxMessageBox(CString(query.c_str()));

if (mysql_query(conn, query.c_str()))
{
AfxMessageBox(_T("SELECT content FROM ReplyContent failed"));
mysql_close(conn);
return replyContent;
}

res = mysql_store_result(conn);
if (res == NULL)
{
AfxMessageBox(_T("mysql_store_result() failed"));
mysql_close(conn);
return replyContent;
}

if ((row = mysql_fetch_row(res)))
{
int len = MultiByteToWideChar(CP_UTF8, 0, row[0], -1, NULL, 0);
if (len > 0)
{
std::wstring wstr(len, L'\0');
MultiByteToWideChar(CP_UTF8, 0, row[0], -1, &wstr[0], len);
replyContent = wstr.c_str();
speak(wstr);
}
else
{
AfxMessageBox(_T("Failed to convert reply content to wide string."));
}
}
else
{
AfxMessageBox(_T("No matching content found."));
}

mysql_free_result(res);
mysql_close(conn);

return replyContent;
}

以下是添加了详细注释的 Levenshtein 距离算法代码:

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
int CSapiASRDlg::levenshteinDistance(const std::wstring& s1, const std::wstring& s2)
{
// 获取两个字符串的长度
const size_t m(s1.size());
const size_t n(s2.size());

// 如果第一个字符串为空,返回第二个字符串的长度
if (m == 0) return n;
// 如果第二个字符串为空,返回第一个字符串的长度
if (n == 0) return m;

// 创建一个 (m+1) x (n+1) 的二维矩阵,用于存储计算结果
std::vector<std::vector<size_t>> matrix(m + 1, std::vector<size_t>(n + 1));

// 初始化第一列,表示将 s1 转换为空字符串的代价
for (size_t i = 0; i <= m; ++i) matrix[i][0] = i;
// 初始化第一行,表示将空字符串转换为 s2 的代价
for (size_t j = 0; j <= n; ++j) matrix[0][j] = j;

// 计算矩阵的其他元素
for (size_t i = 1; i <= m; ++i)
{
for (size_t j = 1; j <= n; ++j)
{
// 如果字符相同,代价为 0,否则代价为 1
size_t cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
// 计算删除、插入和替换操作的代价
size_t deletion = matrix[i - 1][j] + 1;
size_t insertion = matrix[i][j - 1] + 1;
size_t substitution = matrix[i - 1][j - 1] + cost;

// 取三者中的最小值
size_t minValue = deletion;
if (insertion < minValue)
minValue = insertion;
if (substitution < minValue)
minValue = substitution;

// 将计算结果存储在矩阵中
matrix[i][j] = minValue;
}
}

// 最终的 Levenshtein 距离
size_t distance = matrix[m][n];
// 计算两个字符串长度的差异
size_t lengthDifference = std::abs(static_cast<int>(m) - static_cast<int>(n));

// 添加长度差异的惩罚项,使用比例因子调整权重
const float lengthPenaltyFactor = 0.5f; // 可根据需要调整比例因子
size_t lengthPenalty = static_cast<size_t>(lengthDifference * lengthPenaltyFactor);
// 将惩罚项添加到最终距离中
distance += lengthPenalty;

// 返回最终的距离
return distance;
}

float CSapiASRDlg::jaroWinklerDistance(const std::wstring& s1, const std::wstring& s2)
{
const size_t len1 = s1.size();
const size_t len2 = s2.size();

if (len1 == 0 || len2 == 0)
return 0.0f;

// 自定义实现 std::max 和 std::min
auto customMax = [](size_t a, size_t b) -> size_t {
return a > b ? a : b;
};

auto customMin = [](size_t a, size_t b) -> size_t {
return a < b ? a : b;
};

const size_t matchWindow = customMax(len1, len2) / 2 - 1;
std::vector<bool> s1Matches(len1, false);
std::vector<bool> s2Matches(len2, false);
size_t matches = 0;
size_t transpositions = 0;

// Find matches
for (size_t i = 0; i < len1; ++i)
{
const size_t start = customMax(i > matchWindow ? i - matchWindow : 0, static_cast<size_t>(0));
const size_t end = customMin(i + matchWindow + 1, len2);

for (size_t j = start; j < end; ++j)
{
if (s2Matches[j])
continue;

if (s1[i] != s2[j])
continue;

s1Matches[i] = true;
s2Matches[j] = true;
++matches;
break;
}
}

if (matches == 0)
return 0.0f;

size_t k = 0;
for (size_t i = 0; i < len1; ++i)
{
if (!s1Matches[i])
continue;

while (!s2Matches[k])
++k;

if (s1[i] != s2[k])
++transpositions;

++k;
}

const float jaro = (matches / static_cast<float>(len1) +
matches / static_cast<float>(len2) +
(matches - transpositions / 2.0f) / static_cast<float>(matches)) / 3.0f;

// Apply Winkler bonus for common prefix
const size_t prefixLength = customMin(customMin(len1, len2), static_cast<size_t>(4));
size_t commonPrefix = 0;

while (commonPrefix < prefixLength && s1[commonPrefix] == s2[commonPrefix])
++commonPrefix;

return jaro + 0.1f * commonPrefix * (1.0f - jaro);
}


float CSapiASRDlg::cosineSimilarity(const std::wstring& s1, const std::wstring& s2)
{
std::unordered_map<wchar_t, size_t> freq1, freq2;

// 计算频率
for (const auto& c : s1)
++freq1[c];
for (const auto& c : s2)
++freq2[c];

float dotProduct = 0.0f;
float norm1 = 0.0f;
float norm2 = 0.0f;

// 计算点积
for (const auto& [key, value] : freq1)
{
auto it = freq2.find(key);
if (it != freq2.end())
dotProduct += value * it->second;

norm1 += value * value;
}

// 计算范数
for (const auto& [key, value] : freq2)
norm2 += value * value;

norm1 = std::sqrt(norm1);
norm2 = std::sqrt(norm2);

// 处理范数为零的情况
if (norm1 == 0.0f || norm2 == 0.0f)
return 0.0f;

return dotProduct / (norm1 * norm2);
}

float CSapiASRDlg::jaccardSimilarity(const std::wstring& s1, const std::wstring& s2)
{
std::unordered_set<wchar_t> set1(s1.begin(), s1.end());
std::unordered_set<wchar_t> set2(s2.begin(), s2.end());

std::unordered_set<wchar_t> intersection;
std::unordered_set<wchar_t> unionSet(set1);

for (const auto& c : set2)
{
if (set1.find(c) != set1.end())
intersection.insert(c);
unionSet.insert(c);
}

if (unionSet.empty())
return 0.0f;

return static_cast<float>(intersection.size()) / unionSet.size();
}

// Function to compute Sift4 similarity

std::unordered_set<std::wstring> generateCharacterPairs(const std::wstring& str) {
std::unordered_set<std::wstring> pairs;
if (str.size() < 2) return pairs;

for (size_t i = 0; i < str.size() - 1; ++i) {
pairs.insert(str.substr(i, 2));
}
return pairs;
}

float CSapiASRDlg::sift4Similarity(const std::wstring& s1, const std::wstring& s2)
{
// 生成字符对
auto pairs1 = generateCharacterPairs(s1);
auto pairs2 = generateCharacterPairs(s2);

// 计算交集
std::unordered_set<std::wstring> intersection;
std::unordered_set<std::wstring> unionSet(pairs1);

for (const auto& pair : pairs2) {
if (pairs1.find(pair) != pairs1.end()) {
intersection.insert(pair);
}
unionSet.insert(pair);
}

if (unionSet.empty()) return 0.0f;

// 计算相似度
return static_cast<float>(intersection.size()) / unionSet.size();
}

代码解释

  1. 字符串长度获取:

    • const size_t m(s1.size()); 获取第一个字符串的长度。
    • const size_t n(s2.size()); 获取第二个字符串的长度。
  2. 处理空字符串的情况:

    • 如果 s1 为空,返回 s2 的长度。
    • 如果 s2 为空,返回 s1 的长度。
  3. 初始化矩阵:

    • 创建一个大小为 (m+1) x (n+1) 的二维矩阵 matrix,用于存储中间计算结果。
    • 初始化第一列和第一行,表示将字符串转换为空字符串或从空字符串转换的代价。
  4. 填充矩阵:

    • 遍历每个字符,计算删除、插入和替换操作的代价,并取三者中的最小值,存储在矩阵中。
  5. 计算最终的 Levenshtein 距离:

    • size_t distance = matrix[m][n]; 获取最终的 Levenshtein 距离。
    • 计算两个字符串长度的差异,并根据比例因子 lengthPenaltyFactor 调整权重,添加到最终距离中。

通过这种方法,可以在计算 Levenshtein 距离时更好地考虑字符串长度的差异,同时避免相同长度字符串都跑到一个去的问题。

十、语音播报

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

void CSapiASRDlg::speak(const std::wstring& text)
{
CComPtr<ISpVoice> pVoice;

// 初始化 COM 库
HRESULT hr = ::CoInitialize(NULL);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to initialize COM library."));
return;
}

// 创建 SAPI 语音实例
hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&pVoice);
if (SUCCEEDED(hr))
{
// 朗读文本
hr = pVoice->Speak(text.c_str(), SPF_DEFAULT, NULL);
if (FAILED(hr))
{
AfxMessageBox(_T("Speak failed."));
}
}
else
{
AfxMessageBox(_T("Failed to create voice instance."));
}

// 释放资源
::CoUninitialize();
}

十一、数据库账号保存在文件中

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
#include <unordered_set>
#include <fstream>
#include <map>
#include <string>

// 读取配置文件函数
std::map<std::string, std::string> readConfigFile(const std::string& filename)
{
std::ifstream file(filename);
std::map<std::string, std::string> config;
std::string line;

while (std::getline(file, line))
{
size_t pos = line.find('=');
if (pos != std::string::npos)
{
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
config[key] = value;
}
}

return config;
}

func()
{
// 读取配置文件
std::map<std::string, std::string> config = readConfigFile("db_config.txt");

const char* server = config["server"].c_str();
const char* user = config["user"].c_str();
const char* password = config["password"].c_str();
const char* database = config["database"].c_str();
}

导出数据库:

1
2
mysqldump -u root -p VoiceAssistant > VoiceAssistant.sql

其中:

  • -u root 指定 MySQL 用户名为 root
  • -p 提示输入密码。
  • VoiceAssistant 是要导出的数据库名称。
  • > VoiceAssistant.sql 将输出重定向到 VoiceAssistant.sql 文件。

代码

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
#pragma once

#include <sapi.h> // 添加 SAPI 头文件
#pragma warning(push)
#pragma warning(disable: 4996)
#include <sphelper.h>
#include <functional>
#include <memory>
#include <atomic> // 添加 atomic 头文件
#pragma warning(pop)
#include <mysql.h>
#include <vector>
#include <string>



#define WM_USER_UPDATE_TEXT (WM_USER + 1)

class CSapiASRDlg : public CDialogEx
{
public:
CSapiASRDlg(CWnd* pParent = nullptr);
virtual ~CSapiASRDlg();

#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_SAPIASR_DIALOG };
#endif

protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
void OnSysCommand(UINT nID, LPARAM lParam);
void OnPaint();
HCURSOR OnQueryDragIcon();
afx_msg void OnBnClickedButtonStart();
afx_msg void OnBnClickedButtonStop();
afx_msg LRESULT OnUpdateText(WPARAM wParam, LPARAM lParam);
void PerformRecognition();
void connectToDatabase();
int levenshteinDistance(const std::wstring& s1, const std::wstring& s2);
DECLARE_MESSAGE_MAP()

private:
void InitializeSAPI();
void OnRecognition();

HICON m_hIcon;
CComPtr<ISpRecognizer> m_pRecognizer;
CComPtr<ISpRecoContext> m_pRecoContext;
CComPtr<ISpRecoGrammar> m_pGrammar;
CComPtr<ISpAudio> m_cpAudio;
CEdit m_edtText;
CString m_lastRecognizedText;
HANDLE m_hThread;
bool m_bRunning;

std::vector<CString> m_database;
public:
CEdit m_edtResult;
CEdit m_edtIdResult;
CEdit m_edtResponse;
CEdit m_edtIdResponse;
afx_msg void OnBnClickedButtonClear();
void GetReplyContent(const std::string& recognizedText);
CString GetReplyContentById(int testId);
void speak(const std::wstring& text);
};

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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
#include "pch.h"
#include "framework.h"
#include "SapiASR.h"
#include "SapiASRDlg.h"
#include "afxdialogex.h"
#include <sapi.h>
#pragma warning(push)
#pragma warning(disable: 4996)
#include <sphelper.h>
#pragma warning(pop)

#include <atlbase.h>

#include <vector>
#include <string>
#include <algorithm>
#include <unordered_map>

#include <cmath>

#include <unordered_set>
#include <fstream>
#include <map>
#include <string>

// 读取配置文件函数
std::map<std::string, std::string> readConfigFile(const std::string& filename)
{
std::ifstream file(filename);
std::map<std::string, std::string> config;
std::string line;

while (std::getline(file, line))
{
size_t pos = line.find('=');
if (pos != std::string::npos)
{
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
config[key] = value;
}
}

return config;
}


#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define WM_USER_UPDATE_TEXT (WM_USER + 1)

class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();

#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif

protected:
virtual void DoDataExchange(CDataExchange* pDX);

protected:
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()

CSapiASRDlg::CSapiASRDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_SAPIASR_DIALOG, pParent),
m_pRecognizer(NULL),
m_pRecoContext(NULL),
m_pGrammar(NULL),
m_bRunning(false)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
connectToDatabase();
}

void CSapiASRDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_TEXT, m_edtText);
DDX_Control(pDX, IDC_EDIT_RESULT, m_edtResult);
DDX_Control(pDX, IDC_EDIT_RESPONSE, m_edtResponse);
DDX_Control(pDX, IDC_EDIT_ID_RESULT, m_edtIdResult);
DDX_Control(pDX, IDC_EDIT_ID_RESPONSE, m_edtIdResponse);
}

BEGIN_MESSAGE_MAP(CSapiASRDlg, CDialogEx)
ON_MESSAGE(WM_USER_UPDATE_TEXT, &CSapiASRDlg::OnUpdateText)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_START, &CSapiASRDlg::OnBnClickedButtonStart)
ON_BN_CLICKED(IDC_BUTTON_STOP, &CSapiASRDlg::OnBnClickedButtonStop)
ON_BN_CLICKED(IDC_BUTTON_CLEAR, &CSapiASRDlg::OnBnClickedButtonClear)
END_MESSAGE_MAP()

BOOL CSapiASRDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}

SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);

InitializeSAPI();

return TRUE;
}

void CSapiASRDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}

void CSapiASRDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);

SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}

HCURSOR CSapiASRDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}

void CSapiASRDlg::InitializeSAPI()
{
HRESULT hr = ::CoInitialize(NULL);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to initialize COM library."));
return;
}

hr = m_pRecognizer.CoCreateInstance(CLSID_SpInprocRecognizer);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create recognizer: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecognizer->CreateRecoContext(&m_pRecoContext);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create recognition context: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecoContext->CreateGrammar(1, &m_pGrammar);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create grammar: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pGrammar->LoadDictation(NULL, SPLO_STATIC);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to load dictation grammar: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pGrammar->SetDictationState(SPRS_INACTIVE);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set dictation state: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecoContext->SetNotifyWin32Event();
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set notify event: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION));
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set interest for recognition events: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = CoCreateInstance(CLSID_SpMMAudioIn, NULL, CLSCTX_INPROC_SERVER, IID_ISpAudio, (void**)&m_cpAudio);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to create audio input object: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

hr = m_pRecognizer->SetInput(m_cpAudio, TRUE);
if (FAILED(hr))
{
CString message;
message.Format(_T("Failed to set audio input: 0x%08X"), hr);
AfxMessageBox(message);
::CoUninitialize();
return;
}

AfxMessageBox(_T("Initialize SAPI is successful"));
}

CSapiASRDlg::~CSapiASRDlg()
{
if (m_bRunning)
{
m_bRunning = false;
}
if (m_pGrammar)
{
m_pGrammar->SetDictationState(SPRS_INACTIVE);
}
if (m_pRecoContext)
{
m_pRecoContext.Release();
}
if (m_pRecognizer)
{
m_pRecognizer.Release();
}
::CoUninitialize();
}

void CSapiASRDlg::OnBnClickedButtonStart()
{
HRESULT hr = m_pGrammar->SetDictationState(SPRS_ACTIVE);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to start recognition."));
return;
}

m_bRunning = true;
AfxMessageBox(_T("Recognition started."));
}

void CSapiASRDlg::OnBnClickedButtonStop()
{
if (!m_bRunning)
{
AfxMessageBox(_T("Recognition is not started."));
return;
}

HRESULT hr = m_pGrammar->SetDictationState(SPRS_INACTIVE);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to stop recognition."));
return;
}

m_bRunning = false;

// 在这里进行语音识别,并将结果显示在编辑框中
PerformRecognition();

AfxMessageBox(_T("Recognition stopped."));
}

void CSapiASRDlg::PerformRecognition()
{
HRESULT hr = m_pRecoContext->WaitForNotifyEvent(1000); // 1-second timeout
if (FAILED(hr))
{
return;
}

CSpEvent event;
while (event.GetFrom(m_pRecoContext) == S_OK)
{
if (event.eEventId == SPEI_RECOGNITION)
{
ISpRecoResult* pResult = event.RecoResult();
if (pResult)
{
LPWSTR pwszText = nullptr;
hr = pResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, FALSE, &pwszText, NULL);
if (SUCCEEDED(hr))
{
CString* pNewText = new CString(pwszText);
PostMessage(WM_USER_UPDATE_TEXT, reinterpret_cast<WPARAM>(pNewText), 0);
CoTaskMemFree(pwszText);
}
}
}
}
}

void CSapiASRDlg::connectToDatabase()
{
MYSQL* conn;
MYSQL_RES* res;
MYSQL_ROW row;

// 读取配置文件
std::map<std::string, std::string> config = readConfigFile("db_config.txt");

const char* server = config["server"].c_str();
const char* user = config["user"].c_str();
const char* password = config["password"].c_str();
const char* database = config["database"].c_str();

conn = mysql_init(NULL);

// 连接数据库
if (mysql_real_connect(conn, server, user, password, database, 0, NULL, 0) == NULL)
{
AfxMessageBox(_T("mysql_real_connect() failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("mysql_real_connect() success"));
}

// 设置连接的字符集
if (mysql_set_character_set(conn, "utf8mb4"))
{
AfxMessageBox(_T("mysql_set_character_set() failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("mysql is utf8mb4"));
}

// 执行查询
if (mysql_query(conn, "SELECT * FROM RecognizeContent"))
{
AfxMessageBox(_T("SELECT * FROM RecognizeContent failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("SELECT * FROM RecognizeContent success"));
}

res = mysql_store_result(conn);

if (res == NULL)
{
AfxMessageBox(_T("mysql_store_result() failed"));
mysql_close(conn);
return;
}
else
{
AfxMessageBox(_T("mysql_store_result() success"));
}

// 输出查询结果
int num_fields = mysql_num_fields(res);
while ((row = mysql_fetch_row(res)))
{
CString m_data;
for (int i = 0; i < num_fields; i++)
{
if (row[i] == NULL)
{
m_data.Format(_T("NULL"));
continue;
}

// 将 UTF-8 编码的字符串转换为宽字符字符串
int len = MultiByteToWideChar(CP_UTF8, 0, row[i], -1, NULL, 0);
if (len == 0)
{
CString errorMsg;
errorMsg.Format(_T("MultiByteToWideChar failed with error code: %d"), GetLastError());
AfxMessageBox(errorMsg);
continue;
}

std::wstring wstr(len, L'\0');
MultiByteToWideChar(CP_UTF8, 0, row[i], -1, &wstr[0], len);

// 去除最后一个空字符
if (!wstr.empty() && wstr.back() == L'\0') {
wstr.pop_back();
}

m_data.Format(_T("%s"), wstr.c_str());
}
m_database.push_back(m_data);
}

// 释放结果集
mysql_free_result(res);

// 关闭连接
mysql_close(conn);

//// 显示查询结果
//for (const auto& row : m_database)
//{
// AfxMessageBox(row);
//}
}

void CSapiASRDlg::OnBnClickedButtonClear()
{
m_edtText.SetWindowTextW(_T(""));
m_edtResult.SetWindowTextW(_T(""));
m_edtResponse.SetWindowTextW(_T(""));
m_edtIdResult.SetWindowTextW(_T(""));
m_edtIdResponse.SetWindowTextW(_T(""));
}

// Levenshtein 距离算法
int CSapiASRDlg::levenshteinDistance(const std::wstring& s1, const std::wstring& s2)
{
const size_t m(s1.size());
const size_t n(s2.size());

if (m == 0) return n;
if (n == 0) return m;

std::vector<std::vector<size_t>> matrix(m + 1, std::vector<size_t>(n + 1));

for (size_t i = 0; i <= m; ++i) matrix[i][0] = i;
for (size_t j = 0; j <= n; ++j) matrix[0][j] = j;

for (size_t i = 1; i <= m; ++i)
{
for (size_t j = 1; j <= n; ++j)
{
size_t cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
size_t deletion = matrix[i - 1][j] + 1;
size_t insertion = matrix[i][j - 1] + 1;
size_t substitution = matrix[i - 1][j - 1] + cost;

size_t minValue = deletion;
if (insertion < minValue)
minValue = insertion;
if (substitution < minValue)
minValue = substitution;

matrix[i][j] = minValue;
}
}

size_t distance = matrix[m][n];
size_t lengthDifference = std::abs(static_cast<int>(m) - static_cast<int>(n));

// 添加长度差异的惩罚项,使用比例因子调整权重
const float lengthPenaltyFactor = 0.5f; // 可根据需要调整比例因子
size_t lengthPenalty = static_cast<size_t>(lengthDifference * lengthPenaltyFactor);
distance += lengthPenalty;

return distance;
}


LRESULT CSapiASRDlg::OnUpdateText(WPARAM wParam, LPARAM lParam)
{
CString* pNewText = reinterpret_cast<CString*>(wParam);
if (pNewText)
{
m_edtText.SetWindowText(*pNewText);
}

std::wstring newText((*pNewText).GetString());
std::wstring bestMatch;
int bestMatchId = -1;
double minDistance = INT_MAX;

int i = 1;
for (const auto& row_data : m_database)
{
double distance = levenshteinDistance(newText, std::wstring(row_data.GetString()));
//double distance = jaroWinklerDistance(newText, std::wstring(row_data.GetString()));
//double distance = cosineSimilarity(newText, std::wstring(row_data.GetString()));
//double distance = jaccardSimilarity(newText, std::wstring(row_data.GetString()));
//double distance = sift4Similarity(newText, std::wstring(row_data.GetString()));
if (distance < minDistance)
{
minDistance = distance;
bestMatch = row_data.GetString();
bestMatchId = i;
}
i++;
}

// 将最相似的结果输出到编辑框
CString idResult;
idResult.Format(_T("%d"), bestMatchId);
m_edtIdResult.SetWindowText(idResult);
m_edtResult.SetWindowText(bestMatch.c_str());
//CString content = GetReplyContent(bestMatch.c_str());
CString content = GetReplyContentById(bestMatchId);
m_edtIdResponse.SetWindowTextW(idResult);
m_edtResponse.SetWindowTextW(content);

delete pNewText;

return 0;
}


CString CSapiASRDlg::GetReplyContentById(int testId)
{
MYSQL* conn;
MYSQL_RES* res;
MYSQL_ROW row;
CString replyContent;

// 读取配置文件
std::map<std::string, std::string> config = readConfigFile("db_config.txt");

const char* server = config["server"].c_str();
const char* user = config["user"].c_str();
const char* password = config["password"].c_str();
const char* database = config["database"].c_str();

conn = mysql_init(NULL);

if (mysql_real_connect(conn, server, user, password, database, 0, NULL, 0) == NULL)
{
AfxMessageBox(_T("mysql_real_connect() failed"));
mysql_close(conn);
return replyContent;
}

if (mysql_set_character_set(conn, "utf8mb4"))
{
AfxMessageBox(_T("mysql_set_character_set() failed"));
}


// 构建查询语句

std::string query = "SELECT content FROM ReplyContent WHERE id = " + std::to_string(testId);
AfxMessageBox(CString(query.c_str()));

if (mysql_query(conn, query.c_str()))
{
AfxMessageBox(_T("SELECT content FROM ReplyContent failed"));
mysql_close(conn);
return replyContent;
}

res = mysql_store_result(conn);
if (res == NULL)
{
AfxMessageBox(_T("mysql_store_result() failed"));
mysql_close(conn);
return replyContent;
}

if ((row = mysql_fetch_row(res)))
{
int len = MultiByteToWideChar(CP_UTF8, 0, row[0], -1, NULL, 0);
if (len > 0)
{
std::wstring wstr(len, L'\0');
MultiByteToWideChar(CP_UTF8, 0, row[0], -1, &wstr[0], len);
replyContent = wstr.c_str();
speak(wstr);
}
else
{
AfxMessageBox(_T("Failed to convert reply content to wide string."));
}
}
else
{
AfxMessageBox(_T("No matching content found."));
}

mysql_free_result(res);
mysql_close(conn);

return replyContent;
}


void CSapiASRDlg::speak(const std::wstring& text)
{
CComPtr<ISpVoice> pVoice;

// 初始化 COM 库
HRESULT hr = ::CoInitialize(NULL);
if (FAILED(hr))
{
AfxMessageBox(_T("Failed to initialize COM library."));
return;
}

// 创建 SAPI 语音实例
hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&pVoice);
if (SUCCEEDED(hr))
{
// 朗读文本
hr = pVoice->Speak(text.c_str(), SPF_DEFAULT, NULL);
if (FAILED(hr))
{
AfxMessageBox(_T("Speak failed."));
}
}
else
{
AfxMessageBox(_T("Failed to create voice instance."));
}

// 释放资源
::CoUninitialize();
}
-------------本文结束感谢您的阅读-------------