Qt实现在线+离线双模式语音识别项目
摘要:本文基于Qt框架,实现了在线、离线双模式语音识别功能,采用QStackedWidget实现页面导航与切换,集成网络请求完成在线识别、whisper.cpp开源库完成离线识别,解决了不同场景下的语音识别需求,保证无网络环境下仍可正常使用。本文详细介绍项目方案设计、框架搭建、核心功能实现,便于后续复用与扩展。
一、项目概述
1.1 项目需求
开发一款基于Qt的语音识别工具,支持两种识别模式:
-
在线模式:依赖网络接口,将录音数据上传至服务器,获取识别结果(沿用原有在线识别逻辑);
-
离线模式:不依赖网络,基于whisper.cpp开源库,加载本地模型,实现本地录音、本地识别,全程离线运行;
-
界面支持模式切换,布局自适应窗口大小,操作简洁(录音、停止识别、结果显示)。
1.2 技术栈
核心框架:Qt 5/6(主要使用Qt Widgets、Qt Network、Qt Multimedia模块)
在线识别:QNetworkAccessManager(网络请求)、JSON解析(处理服务器返回结果)
离线识别:whisper.cpp(轻量级语音识别开源库)、PCM音频处理
界面布局:QStackedWidget(页面切换)、QVBoxLayout(垂直布局,实现自适应)
二、项目方案设计
2.1 整体方案架构
项目采用“导航页+功能页”的架构,基于QStackedWidget实现页面切换,整体分为3个核心页面,各模块职责清晰、低耦合,便于维护和扩展。
架构流程:程序启动 → 导航页 → 选择在线/离线模式 → 对应功能页(录音→识别→显示结果)→ 可切换模式重复使用。
2.2 页面设计方案
采用QStackedWidget作为页面容器,包含3个页面,布局均使用QVBoxLayout实现自适应,避免控件固定大小导致的窗口缩放异常。
-
导航页(Page 0):放置2个导航按钮(“在线识别”“离线识别”),点击按钮切换至对应功能页,作为程序入口;
-
在线识别页(Page 1):包含2个功能按钮(开始录音、停止识别)和1个文本框(显示识别结果),沿用原有在线识别逻辑;
-
离线识别页(Page 2):与在线页布局一致,控件命名区分(避免冲突),新增离线录音、本地模型识别相关逻辑。
2.3 双模式识别方案
2.3.1 在线识别方案
核心逻辑:基于网络接口,通过Qt的QNetworkAccessManager发送POST请求,将录音获取的PCM原始数据上传至服务器,解析服务器返回的JSON数据,提取识别结果并显示。
流程:开始录音(QAudioInput采集PCM数据)→ 停止录音(获取QBuffer中的PCM数据)→ 网络请求上传数据 → 解析JSON结果 → 显示到文本框。
2.3.2 离线识别方案
核心逻辑:集成whisper.cpp开源库,加载本地预训练模型(ggml-model.bin),本地采集PCM音频数据,转换为模型支持的格式,调用模型接口完成识别,无需网络交互。
流程:程序启动加载模型 → 开始录音(采集16bit、16000Hz、单声道PCM数据)→ 停止录音(获取数据)→ 音频格式转换(int16_t→float,归一化至-1~1)→ 调用whisper识别接口 → 提取识别结果 → 显示到文本框 → 清理资源(便于下次录音)。
三、Qt框架搭建核心细节
3.1 项目结构(核心文件)
3.2 界面布局搭建(关键步骤)
项目目录
├─ mainwindow.h // 头文件:声明控件、槽函数、成员变量
├─ mainwindow.cpp // 核心逻辑:页面切换、录音、识别、结果处理
├─ mainwindow.ui // 界面布局:QStackedWidget、按钮、文本框
├─ SpeechRecognition.pro // 项目配置:引入whisper路径、Qt模块
└─ whisper.cpp-1.2.1 // whisper开源库:包含头文件、源码
├─ include/whisper.h
└─ whisper.cpp、ggml/ggml.c
核心是QStackedWidget的使用和布局自适应,解决“控件无法自适应窗口”“页面切换混乱”的问题,步骤如下:
-
主窗口添加QStackedWidget,设置为中心部件,通过QVBoxLayout套住,实现铺满主窗口并自适应;
-
给QStackedWidget添加3个页面(导航页、在线页、离线页),分别命名便于代码调用;
-
每个页面内部添加对应控件(按钮、文本框),选中所有控件套QVBoxLayout,确保控件自适应页面大小;
-
导航按钮绑定点击事件,通过setCurrentIndex切换页面,实现模式切换。
3.3 核心配置(.pro文件)
关键配置whisper库的路径,确保编译器能找到头文件和源码,避免“找不到whisper.h”的报错:
# 引入Qt核心模块
QT += core gui network multimedia widgets
# whisper头文件路径(根据自身项目目录调整)
INCLUDEPATH += $$PWD/whisper.cpp-1.2.1/include
# whisper源码文件,编译时一起编译
SOURCES += $$PWD/main.cpp \
$$PWD/mainwindow.cpp \
$$PWD/whisper.cpp-1.2.1/whisper.cpp \
$$PWD/whisper.cpp-1.2.1/ggml/ggml.c
# 头文件
HEADERS += $$PWD/mainwindow.h \
$$PWD/whisper.cpp-1.2.1/include/whisper.h
四、核心功能实现(关键代码解析)
4.1 页面切换逻辑(导航核心)
通过QStackedWidget的setCurrentIndex实现页面切换,绑定导航按钮的点击事件,代码简洁且易维护:
// 主窗口构造函数中初始化
ui->stackedWidget->setCurrentIndex(0); // 默认显示导航页
// 在线识别按钮绑定
connect(ui->btnOnline, &QPushButton::clicked, [this](){
ui->stackedWidget->setCurrentIndex(1); // 切换到在线页
});
// 离线识别按钮绑定
connect(ui->btnOffline, &QPushButton::clicked, [this](){
ui->stackedWidget->setCurrentIndex(2); // 切换到离线页
});
4.2 在线识别核心实现(沿用原有逻辑)
重点是录音采集和网络请求,核心代码如下(保留原有逻辑,适配页面切换):
// 在线录音(开始)
void MainWindow::on_recordButton_clicked()
{
// 配置录音格式(16000Hz、单声道、16bit,与服务器要求一致)
QAudioFormat format;
format.setSampleRate(16000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
audioInput = new QAudioInput(format, this);
audioBuffer->open(QIODevice::ReadWrite);
audioInput->start(audioBuffer); // 开始录音,数据存入audioBuffer
}
// 在线识别(停止录音+请求接口)
void MainWindow::on_recognizeButton_clicked()
{
audioInput->stop();
audioBuffer->close();
QByteArray pcmData = audioBuffer->data(); // 获取录音的PCM数据
// 网络请求:上传pcmData到服务器(原有逻辑不变)
QNetworkRequest request;
request.setUrl(QUrl("你的在线识别接口地址"));
netManager->post(request, pcmData);
// 清理资源,便于下次录音
audioBuffer->setData(nullptr, 0);
delete audioInput;
audioInput = nullptr;
}
// 解析服务器返回结果(JSON)
void MainWindow::onNetworkReplyFinished(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
ui->textEdit->setText("在线识别失败:" + reply->errorString());
return;
}
// 解析JSON数据,提取识别结果(原有逻辑不变)
QByteArray resultData = reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(resultData);
QJsonObject obj = doc.object();
QString result = obj.value("result").toString();
ui->textEdit->setText("在线识别结果:" + result);
}
4.3 离线识别核心实现(重点)
核心是whisper模型加载、音频格式转换、模型调用,解决“模型加载”“音频格式不匹配”的核心问题,关键代码解析如下:
// 1. 初始化:加载whisper本地模型(程序启动时执行)
whisper_ctx = whisper_init_from_file("ggml-model.bin");
if (!whisper_ctx) {
QMessageBox::warning(this, "警告", "离线模型加载失败!请检查模型文件路径");
}
// 2. 离线录音(与在线录音格式一致,适配whisper要求)
void MainWindow::on_offlineRecordBtn_clicked()
{
if (!whisper_ctx) return; // 模型未加载,不执行录音
QAudioFormat format;
format.setSampleRate(16000); // whisper要求16000Hz
format.setChannelCount(1); // 单声道
format.setSampleSize(16); // 16bit
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
offlineAudioInput = new QAudioInput(format, this);
offlineAudioBuffer->open(QIODevice::ReadWrite);
offlineAudioInput->start(offlineAudioBuffer);
}
// 3. 离线识别(核心逻辑:音频转换+模型调用)
void MainWindow::on_offlineRecognizeBtn_clicked()
{
if (!whisper_ctx) return;
offlineAudioInput->stop();
QByteArray pcm = offlineAudioBuffer->data(); // 获取录音PCM数据
// 关键:将Qt的QByteArray(PCM)转换为whisper支持的float格式
QVector<float> pcmf32;
// 1. 取出PCM原始数据指针,转换为int16_t(16bit整数)
const int16_t *samples = (const int16_t *)pcm.constData();
// 2. 计算样本数量:PCM数据总长度 ÷ 2(每个int16_t占2字节)
int count = pcm.size() / 2;
// 3. 归一化:将int16_t(-32768~32767)转换为float(-1.0~1.0)
for (int i=0; i<count; i++) {
pcmf32.append(samples[i] / 32768.0f);
}
// 调用whisper模型进行离线识别
whisper_full_params wparams = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
wparams.language = "zh"; // 设置识别语言为中文
whisper_full(whisper_ctx, wparams, pcmf32.data(), pcmf32.size());
// 提取识别结果
QString result;
int n = whisper_full_n_segments(whisper_ctx); // 获取识别片段数量
for (int i=0; i<n; i++) {
result += whisper_full_get_segment_text(whisper_ctx, i); // 拼接片段
}
// 显示结果+清理资源
ui->offlineTextEdit->setText("离线识别结果:" + result);
offlineAudioBuffer->close();
offlineAudioBuffer->setData(nullptr, 0);
delete offlineAudioInput;
offlineAudioInput = nullptr;
}
五、常见问题与解决方案
-
问题1:编译器找不到whisper.h → 解决方案:在.pro文件中正确配置INCLUDEPATH,指向whisper.h所在的include目录,清理项目后重新qmake、编译;
-
问题2:控件无法自适应窗口 → 解决方案:给每个页面、主窗口都套QVBoxLayout,避免控件自由放置,选中控件后右键“Lay Out Vertically”;
-
问题3:离线模型加载失败 → 解决方案:将ggml-model.bin放在exe同级目录,检查模型文件是否完整,路径是否正确;
-
问题4:识别无结果 → 解决方案:检查录音格式是否为16000Hz、单声道、16bit,确保音频格式与whisper要求一致,模型加载成功。
六、项目总结与扩展方向
6.1 项目总结
本项目基于Qt框架,成功实现了在线+离线双模式语音识别,核心亮点:
-
采用QStackedWidget实现页面切换,布局自适应,操作简洁,用户体验良好;
-
在线、离线模式分离,低耦合,可单独维护、扩展,适配不同网络环境;
-
解决了whisper库集成、音频格式转换、模型加载等核心问题,代码可直接复用。
6.2 扩展方向
-
优化识别体验:添加录音时长显示、识别加载动画,提升用户体验;
-
扩展功能:添加识别结果保存(txt文件)、语音播放功能;
-
模型优化:更换更小的whisper模型,减少内存占用,提升识别速度;
-
多语言支持:修改whisper识别参数,支持中英文双语识别。
结尾:本文完整梳理了Qt双模式语音识别项目的方案与实现,核心代码均经过实测可运行,如需完整源码或进一步调试,可留言交流
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)