Browse Source

fix: RMS mic level, 25ms timer, speaker test, overlay recording indicator

- AudioRecorder: peak replaced with RMS (sqrt mean of squares)
- AudioDeviceSettings: level meter timer 100ms -> 25ms
- SoundManager: playRawPcm uses ma_audio_buffer_ref instead of ma_decoder (fixes speaker test)
- InputBar: recording overlay positioned over text input, no layout shift
- messagelist: removed old recording indicator widget
topo_upd
Evgeny 2 months ago
parent
commit
a4ae1c6b41
  1. 2
      tools/chatgui/src/audiodevicesettingspage.cpp
  2. 9
      tools/chatgui/src/audiorecorder.cpp
  3. 65
      tools/chatgui/src/inputbar.cpp
  4. 8
      tools/chatgui/src/inputbar.h
  5. 36
      tools/chatgui/src/messagelist.cpp
  6. 5
      tools/chatgui/src/messagelist.h
  7. 18
      tools/chatgui/src/sound_manager.cpp

2
tools/chatgui/src/audiodevicesettingspage.cpp

@ -114,7 +114,7 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, QWidget* parent)
layout->addStretch();
m_levelTimer = new QTimer(this);
m_levelTimer->setInterval(100);
m_levelTimer->setInterval(25);
connect(m_levelTimer, &QTimer::timeout, this, &AudioDeviceSettingsPage::onLevelTimer);
}

9
tools/chatgui/src/audiorecorder.cpp

@ -5,6 +5,7 @@
#include <QDebug>
#include <cstring>
#include <cstdlib>
#include <cmath>
static AudioRecorder* g_recorder = nullptr;
@ -20,12 +21,12 @@ void audioCaptureCallback(ma_device* pDevice, void* pOutput, const void* pInput,
self->m_pcmBuffer.resize(cur + add);
std::memcpy(self->m_pcmBuffer.data() + cur, src, add * sizeof(int16_t));
float maxAbs = 0.0f;
float sumSq = 0.0f;
for (unsigned int i = 0; i < frameCount * (unsigned int)self->m_channels; i++) {
float v = (float)abs(src[i]) / 32768.0f;
if (v > maxAbs) maxAbs = v;
float v = (float)src[i] / 32768.0f;
sumSq += v * v;
}
self->m_peakLevel = maxAbs;
self->m_peakLevel = sqrtf(sumSq / (float)(frameCount * self->m_channels));
}
AudioRecorder::AudioRecorder(QObject* parent)

65
tools/chatgui/src/inputbar.cpp

@ -1,5 +1,7 @@
#include "inputbar.h"
#include "emojipanel.h"
#include "audiorecorder.h"
#include "voicemessageencoder.h"
#include "debug_ui.h"
#include <QDebug>
#include <QTextEdit>
@ -11,6 +13,8 @@
#include <QFontMetrics>
#include <QScrollBar>
#include <QApplication>
#include <QLabel>
#include <QResizeEvent>
static QPushButton *makeIconBtn(const QString &text, const QString &tooltip, QWidget *parent) {
auto *btn = new QPushButton(text, parent);
@ -99,6 +103,33 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
m_input->setFocus();
});
}
/* Recording overlay — appears over the text input during recording */
m_recordOverlay = new QWidget(this);
m_recordOverlay->setStyleSheet("background: rgba(231, 76, 60, 0.88); border-radius: 6px;");
m_recordOverlay->hide();
auto* roLayout = new QHBoxLayout(m_recordOverlay);
roLayout->setContentsMargins(12, 0, 8, 0);
roLayout->setSpacing(6);
m_recordLabel = new QLabel(m_recordOverlay);
m_recordLabel->setStyleSheet("color: white; font-size: 14px; font-weight: bold; background: transparent;");
roLayout->addWidget(m_recordLabel);
roLayout->addStretch();
m_cancelRecBtn = new QPushButton(QString::fromUtf8("\xE2\x9C\x95"), m_recordOverlay);
m_cancelRecBtn->setFixedSize(24, 24);
m_cancelRecBtn->setStyleSheet(
"QPushButton { border: none; color: white; font-size: 16px; background: transparent; }"
"QPushButton:hover { color: #fdd; }");
connect(m_cancelRecBtn, &QPushButton::clicked, this, [this]() {
if (m_recorder && m_recorder->isRecording()) m_recorder->stopRecording();
});
roLayout->addWidget(m_cancelRecBtn);
if (recorder) {
connect(recorder, &AudioRecorder::recordingStarted, this, &InputBar::showRecordOverlay);
connect(recorder, &AudioRecorder::recordingStopped, this, [this](int) { hideRecordOverlay(); });
connect(recorder, &AudioRecorder::durationChanged, this, &InputBar::updateRecordingUI);
}
}
bool InputBar::eventFilter(QObject *obj, QEvent *event) {
@ -151,9 +182,6 @@ void InputBar::onSendClicked() {
m_input->setFocus();
}
#include "audiorecorder.h"
#include "voicemessageencoder.h"
void InputBar::setRecordingContext(const QString& mediaDirBase, const QString& channelId) {
m_mediaDirBase = mediaDirBase;
m_channelIdForRecord = channelId;
@ -197,3 +225,34 @@ void InputBar::onPttReleased() {
emit sendVoiceMessage(filePath, durationMs);
}
}
void InputBar::resizeEvent(QResizeEvent* event) {
QWidget::resizeEvent(event);
if (m_recordOverlay) {
QRect r = m_input->geometry();
int margin = 4;
m_recordOverlay->setGeometry(r.adjusted(margin, margin, -margin, -margin));
m_recordOverlay->raise();
}
}
void InputBar::showRecordOverlay() {
if (m_recordOverlay) {
m_recordOverlay->setGeometry(m_input->geometry().adjusted(4, 4, -4, -4));
m_recordOverlay->show();
m_recordOverlay->raise();
m_recordLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording 0:00"));
}
}
void InputBar::hideRecordOverlay() {
if (m_recordOverlay) m_recordOverlay->hide();
}
void InputBar::updateRecordingUI(int ms) {
if (m_recordLabel) {
int secs = ms / 1000;
m_recordLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording %1:%2")
.arg(secs / 60).arg(secs % 60, 2, 10, QChar('0')));
}
}

8
tools/chatgui/src/inputbar.h

@ -4,6 +4,7 @@
class QTextEdit;
class QPushButton;
class QLabel;
class EmojiPanel;
class AudioRecorder;
@ -21,12 +22,16 @@ signals:
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
void resizeEvent(QResizeEvent* event) override;
private slots:
void onSendClicked();
void adjustInputHeight();
void onPttPressed();
void onPttReleased();
void updateRecordingUI(int ms);
void showRecordOverlay();
void hideRecordOverlay();
private:
QTextEdit *m_input;
@ -34,6 +39,9 @@ private:
QPushButton *m_pttBtn;
EmojiPanel *m_emojiPanel;
AudioRecorder *m_recorder;
QWidget *m_recordOverlay = nullptr;
QLabel *m_recordLabel = nullptr;
QPushButton *m_cancelRecBtn = nullptr;
bool m_pttActive = false;
QString m_voiceFilePath;
QString m_mediaDirBase;

36
tools/chatgui/src/messagelist.cpp

@ -18,7 +18,6 @@
#include <QApplication>
#include <QDebug>
#include <QFileInfo>
#include <QLabel>
#include <vector>
#include <memory>
@ -171,41 +170,6 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
m_inputBar = new InputBar(m_emojiPanel, recorder, this);
layout->addWidget(m_inputBar);
/* Recording indicator - hidden by default */
m_recordIndicator = new QWidget(this);
m_recordIndicator->setFixedHeight(28);
m_recordIndicator->hide();
auto* recLayout = new QHBoxLayout(m_recordIndicator);
recLayout->setContentsMargins(8, 2, 8, 2);
recLayout->setSpacing(6);
m_recordTimeLabel = new QLabel(m_recordIndicator);
m_recordTimeLabel->setStyleSheet("color: #e74c3c; font-size: 13px; font-weight: bold;");
recLayout->addWidget(m_recordTimeLabel);
recLayout->addStretch();
auto* cancelRecBtn = new QPushButton(QString::fromUtf8("\xE2\x9C\x95"), m_recordIndicator);
cancelRecBtn->setFixedSize(22, 22);
cancelRecBtn->setStyleSheet("QPushButton { border: none; color: #e74c3c; font-size: 14px; background: transparent; }");
connect(cancelRecBtn, &QPushButton::clicked, this, [this]() {
if (m_recorder && m_recorder->isRecording()) m_recorder->stopRecording();
});
recLayout->addWidget(cancelRecBtn);
layout->addWidget(m_recordIndicator);
if (recorder) {
connect(recorder, &AudioRecorder::recordingStarted, this, [this]() {
m_recordIndicator->show();
m_recordTimeLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording 0:00"));
});
connect(recorder, &AudioRecorder::recordingStopped, this, [this](int) {
m_recordIndicator->hide();
});
connect(recorder, &AudioRecorder::durationChanged, this, [this](int ms) {
int secs = ms / 1000;
m_recordTimeLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording %1:%2")
.arg(secs / 60).arg(secs % 60, 2, 10, QChar('0')));
});
}
connect(m_inputBar, &InputBar::sendMessage, this, [this](const QString &text) {
if (m_currentChannelId.isEmpty()) {
qWarning("MessageList: no channel selected, message not sent");

5
tools/chatgui/src/messagelist.h

@ -5,9 +5,9 @@
#include <QHash>
#include <QTimer>
class QLabel;
class ChatView;
class InputBar;
class InputBar;
class EmojiPanel;
class DbManager;
class ChannelList;
@ -45,11 +45,8 @@ private:
bool m_wasAtBottom = false;
QMap<quint64, QString> m_localNicks;
AudioRecorder* m_recorder = nullptr;
QWidget* m_recordIndicator = nullptr;
QLabel* m_recordTimeLabel = nullptr;
QString authorName(quint64 nodeId) const;
void updateRecordingUI();
void restoreScrollPosition(const QString& channelId);
void updateReadPosition();
};

18
tools/chatgui/src/sound_manager.cpp

@ -143,21 +143,21 @@ ma_context* SoundManager::context() {
void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRate, int channels) {
if (!m_engine || !m_initialized) return;
ma_decoder_config decCfg = ma_decoder_config_init(ma_format_s16, (ma_uint32)channels, (ma_uint32)sampleRate);
ma_decoder* decoder = new ma_decoder;
ma_result r = ma_decoder_init_memory(pcm, (ma_uint64)sampleCount * channels * sizeof(int16_t), &decCfg, decoder);
ma_audio_buffer_ref* ref = new ma_audio_buffer_ref;
ma_result r = ma_audio_buffer_ref_init(ma_format_s16, (ma_uint32)channels,
pcm, (ma_uint64)sampleCount, ref);
if (r != MA_SUCCESS) {
qWarning("SoundManager: PCM decoder init failed (%d)", r);
delete decoder;
qWarning("SoundManager: audio_buffer_ref_init failed (%d)", r);
delete ref;
return;
}
ma_sound* sound = new ma_sound;
r = ma_sound_init_from_data_source(m_engine, (ma_data_source*)decoder, 0, nullptr, sound);
r = ma_sound_init_from_data_source(m_engine, (ma_data_source*)ref, 0, nullptr, sound);
if (r != MA_SUCCESS) {
qWarning("SoundManager: PCM sound init failed (%d)", r);
ma_decoder_uninit(decoder);
delete decoder;
ma_audio_buffer_ref_uninit(ref);
delete ref;
delete sound;
return;
}
@ -165,8 +165,6 @@ void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRat
ma_sound_set_volume(sound, m_volume);
ma_sound_start(sound);
qDebug("SoundManager: playing PCM (%d samples, %dHz, %dch)", sampleCount, sampleRate, channels);
/* auto-cleanup after playback ends. Note: decoder+sound leak on early shutdown, acceptable for voice messages */
}
QVector<SoundManager::DeviceInfo> SoundManager::enumeratePlaybackDevices() {

Loading…
Cancel
Save