Files
live-caption/apps/gui/main.cpp
T
blumia bd921b1ac5 feat(asr): add pluggable ONNX streaming backend and model registry
Add a second backend (sherpa-onnx) alongside llama.cpp, so native
streaming models can be selected by ID from a JSON registry. All such
models share one adapter: only the model files and a few options differ.
Models now live under the app data directory and are fetched by
scripts/fetch-models.sh. Adding a model is a data change, not code.

Measured on the same audio: the streaming backend is ~35x cheaper in CPU
than Qwen3-ASR (0.10-0.12 vs 4.27 core-seconds per audio second), shows
the first caption at 0.48-1.44s, and matches its accuracy for zh/en.

新增 sherpa-onnx 流式后端,与 llama.cpp 并存;模型按注册表 id 从应用
数据目录加载。同族模型共用一个适配器,新增模型只改 data/models.json。
实测流式后端 CPU 约为 Qwen3-ASR 的 1/35,首个字幕 0.48~1.44s,中英
准确率持平。注意预编译库是旧 C++ ABI,必须走其 C API。
2026-10-12 16:06:51 +08:00

112 lines
4.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 图形界面版:悬浮字幕窗。
#include <cstdio>
#include <QApplication>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QGuiApplication>
#include <QTimer>
#include "appcli.h"
#include "captionwindow.h"
#include "livetranscriber.h"
#include "iasr.h"
#include "qwen3asr.h"
#include "streamingasr.h"
#include "transcript.h"
namespace {
int runGui(lc::IAsr & asr, const lc::StreamOptions & sop,
const QString & screenshotPath, int screenshotDelaySec, int opacityPercent)
{
lc::CaptionWindow window;
window.setPanelOpacity(opacityPercent);
lc::LiveTranscriber live(asr);
QObject::connect(&live, &lc::LiveTranscriber::caption, &window,
[&window](const QString & committed, const QString & tail,
const QString & lang) {
window.setLanguage(
QString::fromStdString(lc::languageDisplayName(lang.toStdString())));
window.setCaption(committed, tail);
});
QObject::connect(&live, &lc::LiveTranscriber::failed, &window, [](const QString & m) {
qWarning("采集失败: %s", qUtf8Printable(m));
});
QObject::connect(&window, &lc::CaptionWindow::closed, qApp, &QCoreApplication::quit);
QObject::connect(&window, &lc::CaptionWindow::languageClicked, &window, [] {
qInfo() << "语言标签被点击(设置界面将在 Phase 4 接入)";
});
QString err;
if (!live.start(sop, &err)) {
qCritical("启动采集失败: %s", qUtf8Printable(err));
return 1;
}
qInfo("正在采集: %s", qUtf8Printable(live.deviceName()));
window.show();
if (!screenshotPath.isEmpty()) {
// 自截图:用于验证渲染(不依赖外部截图工具)
QTimer::singleShot(screenshotDelaySec * 1000, &window, [&window, screenshotPath] {
const bool ok1 = window.grab().save(screenshotPath);
window.setCollapsed(false);
QString expandedPath = screenshotPath;
expandedPath.replace(QStringLiteral(".png"), QStringLiteral("-expanded.png"));
const bool ok2 = window.grab().save(expandedPath);
std::fprintf(stderr, "截图: 折叠态 %s / 展开态 %s\n",
ok1 ? "成功" : "失败", ok2 ? "成功" : "失败");
QCoreApplication::quit();
});
}
return QCoreApplication::exec();
}
} // namespace
int main(int argc, char ** argv)
{
// 说明意图:DPI 取整策略必须在 QGuiApplication 之前定。
// (Deepin 的会话环境已经设了 QT_SCALE_FACTOR_ROUNDING_POLICY,
// 所以本机仍会看到 Qt 的那条提醒,那不是本程序的问题。)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
QApplication app(argc, argv);
QCoreApplication::setApplicationName(QStringLiteral("live-caption"));
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("实时字幕(悬浮窗)"));
parser.addHelpOption();
lc::app::addModelOptions(parser);
lc::app::addStreamOptions(parser);
lc::app::addBackendOptions(parser);
parser.addOption(lc::app::makeOption("opacity", "字幕背景不透明度,取值 20-100", "n", "84"));
parser.addOption(lc::app::makeOption("screenshot", "启动后自动截图并退出(验证渲染用)", "path"));
parser.addOption(lc::app::makeOption("screenshot-delay", "截图前等待秒数", "sec", "6"));
parser.process(app);
const lc::AsrOptions asrOpt = lc::app::asrOptions(parser);
lc::StreamOptions sop = lc::app::streamOptions(parser);
std::string err;
std::unique_ptr<lc::IAsr> asr = lc::app::makeAsr(parser, asrOpt, &err);
if (!asr) {
std::fprintf(stderr, "%s\n", err.c_str());
return 1;
}
if (asr->kind() == lc::AsrKind::NativeStreaming && !parser.isSet("stream-chunk")) {
sop.chunkSec = 0.32;
}
return runGui(*asr, sop,
parser.value("screenshot"),
parser.value("screenshot-delay").toInt(),
parser.value("opacity").toInt());
}