1. 增加了录音的按钮
This commit is contained in:
@@ -5,6 +5,9 @@
|
|||||||
#if !defined(EMBEDDED_LINUX)
|
#if !defined(EMBEDDED_LINUX)
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
#include <QPushButton>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -85,6 +88,9 @@ private:
|
|||||||
#if !defined(EMBEDDED_LINUX)
|
#if !defined(EMBEDDED_LINUX)
|
||||||
Menu *contextMenu; /// 使用 Menu 类
|
Menu *contextMenu; /// 使用 Menu 类
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
QPushButton *pttButton; /// 嵌入式平台PTT按钮
|
||||||
|
#endif
|
||||||
|
|
||||||
bool isLeftPressed; /// 鼠标左键是否按下
|
bool isLeftPressed; /// 鼠标左键是否按下
|
||||||
bool isRightPressed; /// 鼠标右键是否按下
|
bool isRightPressed; /// 鼠标右键是否按下
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "AudioDataHandle.h"
|
#include "AudioDataHandle.h"
|
||||||
|
#if !defined(EMBEDDED_LINUX)
|
||||||
#include "AutoAgentHandle.h"
|
#include "AutoAgentHandle.h"
|
||||||
#include "ScreenShotReqDataHandle.h"
|
#include "ScreenShotReqDataHandle.h"
|
||||||
|
#endif
|
||||||
#include "DeviceDataHandle.h"
|
#include "DeviceDataHandle.h"
|
||||||
|
|
||||||
#include "AudioInput.h"
|
#include "AudioInput.h"
|
||||||
@@ -88,13 +90,31 @@ AppCore::AppCore(QObject *parent) : QObject(parent)
|
|||||||
|
|
||||||
// 初始化业务解析单例
|
// 初始化业务解析单例
|
||||||
AudioDataHandle::getInstance();
|
AudioDataHandle::getInstance();
|
||||||
|
#if !defined(EMBEDDED_LINUX)
|
||||||
AutoAgentHandle::getInstance();
|
AutoAgentHandle::getInstance();
|
||||||
ScreenShotReqDataHandle::getInstance();
|
ScreenShotReqDataHandle::getInstance();
|
||||||
|
#endif
|
||||||
// 注入发送接口
|
// 注入发送接口
|
||||||
NetworkDO::getInstance()->registerSender([](const QString& type, const QJsonObject& data){
|
NetworkDO::getInstance()->registerSender([](const QString& type, const QJsonObject& data){
|
||||||
WebSocketClient::getInstance()->sendJson(type, data);
|
WebSocketClient::getInstance()->sendJson(type, data);
|
||||||
});
|
});
|
||||||
// TODO Test
|
#ifdef EMBEDDED_LINUX
|
||||||
|
// 嵌入式平台无Settings UI,需手动绑定WebSocket接收链路
|
||||||
|
connect(WebSocketClient::getInstance(), &WebSocketClient::jsonReceived,
|
||||||
|
NetworkDO::getInstance(), &NetworkDO::onDataReceived);
|
||||||
|
// 自动配置并连接服务端(优先读取环境变量 YOSUGA_SERVER_URL)
|
||||||
|
{
|
||||||
|
QString serverUrl = qEnvironmentVariable("YOSUGA_SERVER_URL", "ws://192.168.1.18:8765");
|
||||||
|
auto *client = WebSocketClient::getInstance();
|
||||||
|
client->setConfiguration(QUrl(serverUrl));
|
||||||
|
client->setAutoReconnect(true);
|
||||||
|
client->connectToServer();
|
||||||
|
qDebug() << "[AppCore] Embedded: connecting to server:" << serverUrl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
AudioInput::getInstance()->setAudioSettings(16000, 2);
|
||||||
|
#endif
|
||||||
AudioInput::getInstance()->setAudioPath(QDir::currentPath(), "/temp.wav");
|
AudioInput::getInstance()->setAudioPath(QDir::currentPath(), "/temp.wav");
|
||||||
// 连接必要的信号
|
// 连接必要的信号
|
||||||
connect(AudioInput::getInstance(), &AudioInput::recordingFinished_Byte,
|
connect(AudioInput::getInstance(), &AudioInput::recordingFinished_Byte,
|
||||||
@@ -114,8 +134,10 @@ AppCore::~AppCore()
|
|||||||
if (m_deviceTcpServer) m_deviceTcpServer->stop();
|
if (m_deviceTcpServer) m_deviceTcpServer->stop();
|
||||||
if (m_deviceWsServer) m_deviceWsServer->stop();
|
if (m_deviceWsServer) m_deviceWsServer->stop();
|
||||||
|
|
||||||
|
#if !defined(EMBEDDED_LINUX)
|
||||||
ScreenShotReqDataHandle::destroy();
|
ScreenShotReqDataHandle::destroy();
|
||||||
AutoAgentHandle::destroy();
|
AutoAgentHandle::destroy();
|
||||||
|
#endif
|
||||||
AudioDataHandle::destroy();
|
AudioDataHandle::destroy();
|
||||||
DeviceDataHandle::destroy();
|
DeviceDataHandle::destroy();
|
||||||
|
|
||||||
|
|||||||
+33
-1
@@ -14,8 +14,11 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "TextRenderer.h"
|
#include "TextRenderer.h"
|
||||||
// #include "AudioOutput.h"
|
|
||||||
#include "AppContext.h"
|
#include "AppContext.h"
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
#include "AppCore.h"
|
||||||
|
#include <QIcon>
|
||||||
|
#endif
|
||||||
QMap<QString, double> GLCore::frameRateMap = {
|
QMap<QString, double> GLCore::frameRateMap = {
|
||||||
{"30", 30.0},
|
{"30", 30.0},
|
||||||
{"60", 60.0},
|
{"60", 60.0},
|
||||||
@@ -87,6 +90,35 @@ GLCore::GLCore(const int width, const int height, QWidget *parent)
|
|||||||
connect(contextMenu, &Menu::closeMainWindow, this, &GLCore::closeGL); // 关闭窗口信号
|
connect(contextMenu, &Menu::closeMainWindow, this, &GLCore::closeGL); // 关闭窗口信号
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
AppCore::getInstance();
|
||||||
|
|
||||||
|
pttButton = new QPushButton(this);
|
||||||
|
pttButton->setFixedSize(64, 64);
|
||||||
|
pttButton->move(width - 80, height - 80);
|
||||||
|
pttButton->setIcon(QIcon("Resources/Pic/Others/voice.png"));
|
||||||
|
pttButton->setIconSize(QSize(56, 56));
|
||||||
|
pttButton->setStyleSheet(
|
||||||
|
"QPushButton {"
|
||||||
|
" background-color: rgba(255, 255, 255, 60);"
|
||||||
|
" border-radius: 32px;"
|
||||||
|
" border: none;"
|
||||||
|
"}"
|
||||||
|
"QPushButton:pressed {"
|
||||||
|
" background-color: rgba(250, 80, 80, 150);"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
pttButton->raise();
|
||||||
|
pttButton->show();
|
||||||
|
|
||||||
|
connect(pttButton, &QPushButton::pressed, this, []() {
|
||||||
|
AppCore::getInstance()->startPttRecording();
|
||||||
|
});
|
||||||
|
connect(pttButton, &QPushButton::released, this, []() {
|
||||||
|
AppCore::getInstance()->stopPttRecording();
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
// 注册当前实例到中介类
|
// 注册当前实例到中介类
|
||||||
AppContext::RegisterGLCore(this);
|
AppContext::RegisterGLCore(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
#include <QProcess>
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 录音模块
|
* @brief 录音模块
|
||||||
@@ -150,5 +153,8 @@ private:
|
|||||||
qreal m_smoothRms = 0.0; /// 平滑RMS值(用于防止低频杂波突然打断静音检测)
|
qreal m_smoothRms = 0.0; /// 平滑RMS值(用于防止低频杂波突然打断静音检测)
|
||||||
|
|
||||||
bool m_hasVoiceDetected = false; /// 是否已检测到人声
|
bool m_hasVoiceDetected = false; /// 是否已检测到人声
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
QProcess *m_arecordProcess = nullptr; /// 嵌入式平台使用arecord录音
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ void AudioInput::setAudioSettings(const int rate, const int channels)
|
|||||||
{
|
{
|
||||||
m_format.setSampleRate(rate);
|
m_format.setSampleRate(rate);
|
||||||
m_format.setChannelCount(channels);
|
m_format.setChannelCount(channels);
|
||||||
// 为了生成标准 WAV 且方便计算 RMS,强制设为 Int16
|
|
||||||
m_format.setSampleFormat(QAudioFormat::Int16);
|
m_format.setSampleFormat(QAudioFormat::Int16);
|
||||||
|
|
||||||
// 检查设备是否支持该格式,不支持则使用最接近的
|
// 检查设备是否支持该格式,不支持则使用最接近的
|
||||||
@@ -72,6 +71,30 @@ void AudioInput::setAudioPath(const QString &path, const QString &fileName)
|
|||||||
|
|
||||||
void AudioInput::startAudio()
|
void AudioInput::startAudio()
|
||||||
{
|
{
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
m_rawPCMData.clear();
|
||||||
|
if (!m_arecordProcess) {
|
||||||
|
m_arecordProcess = new QProcess(this);
|
||||||
|
connect(m_arecordProcess, &QProcess::readyReadStandardOutput, this, [this]() {
|
||||||
|
m_rawPCMData.append(m_arecordProcess->readAllStandardOutput());
|
||||||
|
});
|
||||||
|
} else if (m_arecordProcess->state() != QProcess::NotRunning) {
|
||||||
|
m_arecordProcess->terminate();
|
||||||
|
m_arecordProcess->waitForFinished(1000);
|
||||||
|
}
|
||||||
|
m_arecordProcess->start("arecord", {
|
||||||
|
"-D", "hw:0,0", "-f", "S16_LE",
|
||||||
|
"-r", QString::number(m_format.sampleRate()),
|
||||||
|
"-c", QString::number(m_format.channelCount()),
|
||||||
|
"-t", "raw", "-q", "-"
|
||||||
|
});
|
||||||
|
if (m_arecordProcess->waitForStarted(1000)) {
|
||||||
|
qDebug() << "Started recording via arecord: hw:0,0"
|
||||||
|
<< m_format.sampleRate() << "Hz" << m_format.channelCount() << "ch S16_LE";
|
||||||
|
} else {
|
||||||
|
qCritical() << "Failed to start arecord";
|
||||||
|
}
|
||||||
|
#else
|
||||||
// 每次开始前重新创建 QAudioSource,确保状态重置
|
// 每次开始前重新创建 QAudioSource,确保状态重置
|
||||||
if (m_audioSource) {
|
if (m_audioSource) {
|
||||||
delete m_audioSource;
|
delete m_audioSource;
|
||||||
@@ -79,8 +102,6 @@ void AudioInput::startAudio()
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_audioSource = new QAudioSource(m_currentDevice, m_format, this);
|
m_audioSource = new QAudioSource(m_currentDevice, m_format, this);
|
||||||
|
|
||||||
// 调大缓冲区以避免溢出
|
|
||||||
m_audioSource->setBufferSize(128000);
|
m_audioSource->setBufferSize(128000);
|
||||||
|
|
||||||
// start() 返回一个 QIODevice,可以从中读取数据
|
// start() 返回一个 QIODevice,可以从中读取数据
|
||||||
@@ -92,14 +113,21 @@ void AudioInput::startAudio()
|
|||||||
} else {
|
} else {
|
||||||
qCritical() << "Failed to start audio recording.";
|
qCritical() << "Failed to start audio recording.";
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInput::stopAudio()
|
void AudioInput::stopAudio()
|
||||||
{
|
{
|
||||||
|
#ifdef EMBEDDED_LINUX
|
||||||
|
if (m_arecordProcess && m_arecordProcess->state() != QProcess::NotRunning) {
|
||||||
|
m_arecordProcess->terminate();
|
||||||
|
m_arecordProcess->waitForFinished(2000);
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (m_audioSource) {
|
if (m_audioSource) {
|
||||||
m_audioSource->stop();
|
m_audioSource->stop();
|
||||||
// 注意:不要立即 delete m_audioSource,某些情况下可能导致 crash,停止即可
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// 停止所有定时器
|
// 停止所有定时器
|
||||||
m_timer->stop();
|
m_timer->stop();
|
||||||
|
|||||||
@@ -29,27 +29,24 @@ void StreamAudioWorker::start(int sampleRate, int channelCount, int bitDepth) {
|
|||||||
|
|
||||||
m_firstChunk = true;
|
m_firstChunk = true;
|
||||||
|
|
||||||
// 配置音频格式
|
|
||||||
QAudioFormat format;
|
QAudioFormat format;
|
||||||
format.setSampleRate(sampleRate);
|
format.setSampleRate(sampleRate);
|
||||||
format.setChannelCount(channelCount);
|
format.setChannelCount(channelCount);
|
||||||
|
|
||||||
if (bitDepth == 8) format.setSampleFormat(QAudioFormat::UInt8);
|
if (bitDepth == 8) format.setSampleFormat(QAudioFormat::UInt8);
|
||||||
else if (bitDepth == 16) format.setSampleFormat(QAudioFormat::Int16);
|
else if (bitDepth == 16) format.setSampleFormat(QAudioFormat::Int16);
|
||||||
else if (bitDepth == 32) format.setSampleFormat(QAudioFormat::Float); // 32位通常为Float
|
else if (bitDepth == 32) format.setSampleFormat(QAudioFormat::Float);
|
||||||
else format.setSampleFormat(QAudioFormat::Int16); // 默认回退
|
else format.setSampleFormat(QAudioFormat::Int16);
|
||||||
|
|
||||||
// 检查设备是否支持
|
|
||||||
auto device = QMediaDevices::defaultAudioOutput();
|
auto device = QMediaDevices::defaultAudioOutput();
|
||||||
if (!device.isFormatSupported(format)) {
|
if (!device.isFormatSupported(format)) {
|
||||||
qWarning() << "[Worker] Device does not support format, using preferred format.";
|
qWarning() << "[Worker] Device does not support format, using preferred format.";
|
||||||
format = device.preferredFormat();
|
format = device.preferredFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 Sink (必须在 Worker 线程中创建)
|
|
||||||
m_sink.reset(new QAudioSink(device, format));
|
m_sink.reset(new QAudioSink(device, format));
|
||||||
|
m_sink->setBufferSize(sampleRate * channelCount * (bitDepth / 8));
|
||||||
|
|
||||||
// 监听状态
|
|
||||||
connect(m_sink.data(), &QAudioSink::stateChanged, this, [this](QAudio::State state){
|
connect(m_sink.data(), &QAudioSink::stateChanged, this, [this](QAudio::State state){
|
||||||
if (state == QAudio::IdleState) {
|
if (state == QAudio::IdleState) {
|
||||||
emit playbackFinished();
|
emit playbackFinished();
|
||||||
@@ -61,32 +58,34 @@ void StreamAudioWorker::start(int sampleRate, int channelCount, int bitDepth) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 启动,获取 IO 设备
|
|
||||||
m_ioDevice = m_sink->start();
|
m_ioDevice = m_sink->start();
|
||||||
if (!m_ioDevice) {
|
if (!m_ioDevice) {
|
||||||
emit errorOccurred("Failed to start audio device");
|
emit errorOccurred("Failed to start audio device");
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "[Worker] Stream started:" << sampleRate << "Hz";
|
qDebug() << "[Worker] Stream started:" << sampleRate << "Hz, buffer:" << m_sink->bufferSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamAudioWorker::processChunk(const QByteArray& chunk) {
|
void StreamAudioWorker::processChunk(const QByteArray& chunk) {
|
||||||
if (chunk.isEmpty() || !m_ioDevice || !m_sink) return;
|
if (chunk.isEmpty() || !m_ioDevice || !m_sink) return;
|
||||||
QByteArray dataToWrite = chunk;
|
QByteArray dataToWrite = chunk;
|
||||||
// 智能处理 WAV 头
|
|
||||||
if (m_firstChunk) {
|
if (m_firstChunk) {
|
||||||
if (hasWavHeader(chunk)) {
|
if (hasWavHeader(chunk)) {
|
||||||
qDebug() << "[Worker] Detected WAV header, stripping 44 bytes.";
|
|
||||||
dataToWrite = chunk.mid(44);
|
dataToWrite = chunk.mid(44);
|
||||||
}
|
}
|
||||||
m_firstChunk = false;
|
m_firstChunk = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入音频设备 (QAudioSink 内部有缓冲区,这里直接 write 即可)
|
const char *ptr = dataToWrite.constData();
|
||||||
// 如果数据量过大,write 可能会阻塞,但在独立线程中这是可以接受的
|
qint64 remaining = dataToWrite.size();
|
||||||
qint64 written = m_ioDevice->write(dataToWrite);
|
while (remaining > 0) {
|
||||||
if (written != dataToWrite.size()) {
|
qint64 written = m_ioDevice->write(ptr, remaining);
|
||||||
qWarning() << "[Worker] Incomplete write:" << written << "/" << dataToWrite.size();
|
if (written > 0) {
|
||||||
|
ptr += written;
|
||||||
|
remaining -= written;
|
||||||
|
} else {
|
||||||
|
QThread::msleep(5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user