Browse Source

unify voice waveform format: base64 wf={100chars};dur={ms}; for both desktop and Android

- inputbar.cpp: CSV waveform → base64 log-scale (same as voice_recorder.c)
- messagelist.cpp: sendVoiceMessage removes displayName|dur_sec| prefix, uses wfStr directly
  setVoiceMessageRoles: decodes new format wf={base64};dur={ms};|... from data field
- messagedelegate.cpp: waveform values now pre-normalized 0..1, no log recalculation needed
- VoiceEncoder: no changes needed (was already compatible)

Both platforms now store identical format in chat_msg_submit.data:
  wf=ABCDef...100chars;dur=NNNN;|file_size|block_size|num_blocks|sigs_hex
topo_upd
evgeny 2 months ago
parent
commit
4ffa59d968
  1. 10
      tools/chatgui/src/inputbar.cpp
  2. 9
      tools/chatgui/src/messagedelegate.cpp
  3. 72
      tools/chatgui/src/messagelist.cpp

10
tools/chatgui/src/inputbar.cpp

@ -250,7 +250,8 @@ void InputBar::onPttReleased() {
m_voiceFilePath = QString("%1/%2_%3_%4.%5").arg(mediaPath, blk.dt, blk.basename, blk.suffix, blk.ext);
const auto& wf = m_recorder->waveformLevels();
QString wfStr;
QString wfStr = "wf=";
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int N = (int)wf.size();
for (int i = 0; i < 100; i++) {
int s = i * N / 100, e = (i + 1) * N / 100;
@ -260,9 +261,12 @@ void InputBar::onPttReleased() {
float sum = 0; int cnt = 0;
for (int j = s; j < e; j++) { sum += wf[j]; cnt++; }
float v = cnt > 0 ? sum / cnt : 0;
if (i > 0) wfStr += ',';
wfStr += QString::number(v, 'f', 3);
float db = v > 0.00001f ? 20.0f * log10f(v) : -100.0f;
int idx = (int)((db + 60.0f) / 60.0f * 64.0f);
if (idx < 0) idx = 0; if (idx > 63) idx = 63;
wfStr += b64[idx];
}
wfStr += QString(";dur=%1;").arg(durationMs);
GUI_INFO("InputBar: voice message ready: dt=%s stem=%s blocks=%d dur=%.1fs",
qPrintable(blk.dt), qPrintable(blk.basename), blk.numBlocks, durationSec);
emit sendVoiceMessage(blk.dt, blk.basename, blk.suffix, blk.ext,

9
tools/chatgui/src/messagedelegate.cpp

@ -710,10 +710,7 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
float barW = (float)wfR.width() / wfN;
int ph = wfR.height() - 3;
for (int i = 0; i < wfN; i++) {
float v = wf[i].toFloat();
float db = v > 0.00001f ? 20.0f * log10f(v) : -100.0f;
float logV = (db + 60.0f) / 60.0f;
if (logV < 0) logV = 0; if (logV > 1) logV = 1;
float logV = wf[i].toFloat(); /* already 0..1 normalized */
int barH = 2 + (int)(logV * (ph - 2));
int x = wfR.left() + (int)(i * barW);
int y = wfR.top() + ph - barH;
@ -724,8 +721,8 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
QColor idle(0x70, 0x70, 0x78, 130);
painter->setBrush((isPlaying || isPaused) ? (before ? played : unplayed) : idle);
painter->drawRect(QRect(x, y, bw, barH));
if (i == 0) GUI_DEBUG("voice paint: wfN=%d wfW=%d v=%.3f dB=%.1f logV=%.2f barH=%d",
wfN, wfR.width(), v, db, logV, barH);
if (i == 0) GUI_DEBUG("voice paint: wfN=%d wfW=%d logV=%.2f barH=%d",
wfN, wfR.width(), logV, barH);
}
}

72
tools/chatgui/src/messagelist.cpp

@ -13,6 +13,8 @@
#include <QPainter>
#include <QPixmap>
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFile>
#include <QScrollBar>
#include <QApplication>
@ -95,23 +97,26 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
if (ct == "audio/opus") {
QString raw = QString::fromUtf8(data);
int pipe1 = raw.indexOf('|');
if (pipe1 < 0 && raw.startsWith('{')) return;
int pipe2 = raw.indexOf('|', pipe1 + 1);
QString fname = pipe1 >= 0 ? raw.left(pipe1) : raw;
QString dur = pipe2 >= 0 ? raw.mid(pipe1 + 1, pipe2 - pipe1 - 1) : raw.mid(pipe1 + 1);
QString rest = pipe2 >= 0 ? raw.mid(pipe2 + 1) : QString();
/* find end of wf (comma-separated floats), then file metadata */
int afterWf = -1;
int pipeCount = 0;
for (int i = 0; i < rest.size(); i++) {
if (rest[i] == '|') { pipeCount++; if (pipeCount >= 4) { afterWf = i; break; } }
}
if (raw.startsWith('{')) return;
/* unified format: wf={base64};dur={ms};|{file_size}|{block_size}|{num_blocks}|{sigs} */
QString wfData;
if (afterWf >= 0) {
wfData = rest.left(afterWf);
QString meta = rest.mid(afterWf + 1);
int durMs = 0;
if (raw.startsWith("wf=")) {
int durPos = raw.indexOf(";dur=", 3);
int durEnd = raw.indexOf(';', durPos + 5);
if (durPos > 0 && durEnd > 0) {
wfData = raw.mid(3, durPos - 3); /* 100 base64 chars */
durMs = raw.mid(durPos + 5, durEnd - durPos - 5).toInt();
}
}
item->setData(QString::number(durMs / 1000.0, 'f', 1) + "s", MsgVoiceDurationRole);
/* block metadata after last ; */
int pipePos = raw.indexOf('|');
if (pipePos > 0) {
QString meta = raw.mid(pipePos + 1);
QStringList parts = meta.split('|');
if (parts.size() >= 4) {
item->setData(parts[0].toULongLong(), MsgVoiceFileSizeRole);
@ -119,25 +124,32 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
item->setData(parts[2].toUInt(), MsgVoiceNumBlocksRole);
item->setData(parts[3], MsgVoiceSigsRole);
}
} else {
wfData = rest;
}
if (!dur.isEmpty()) item->setData(dur + "s", MsgVoiceDurationRole);
if (!fname.isEmpty()) {
QString fullPath = mediaDirBase + "/" + channelId + "/" + fname;
/* file path from local_attrs */
QJsonObject la = QJsonDocument::fromJson(localAttrs).object();
QString fp = la.value("fp").toString();
if (!fp.isEmpty()) {
QString fullPath = mediaDirBase + "/" + channelId + "/" + fp;
item->setData(fullPath, MsgVoiceFileRole);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "setVoiceMessageRoles: voice file=%s dur=%s wf=%d exists=%d",
qPrintable(fullPath), qPrintable(dur),
wfData.split(',').size(), QFile::exists(fullPath));
} else {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "setVoiceMessageRoles: empty filename, raw='%s'", qPrintable(raw));
}
/* decode base64 waveform to normalized 0..1 float list */
if (!wfData.isEmpty()) {
QList<QVariant> wf;
for (const auto& s : wfData.split(','))
wf.append(s.toDouble());
for (int i = 0; i < wfData.size(); i++) {
QChar c = wfData[i];
int idx = 0;
if (c >= 'A' && c <= 'Z') idx = c.unicode() - 'A';
else if (c >= 'a' && c <= 'z') idx = c.unicode() - 'a' + 26;
else if (c >= '0' && c <= '9') idx = c.unicode() - '0' + 52;
else if (c == '+') idx = 62;
else if (c == '/') idx = 63;
wf.append((float)idx / 63.0f);
}
item->setData(wf, MsgVoiceWaveformRole);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "setVoiceMessageRoles: voice dur=%dms wf=%d exists=%d",
durMs, wf.size(), QFile::exists(fp));
}
}
if (ct == "application/octet-stream") {
@ -240,9 +252,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
int durationMs, const QString& wfStr) {
if (m_currentChannelId.isEmpty()) return;
QString displayName = basename + "." + ext;
QByteArray textData = (displayName + "|" +
QString::number(durationMs / 1000.0, 'f', 1) + "|" + wfStr).toUtf8();
QByteArray textData = wfStr.toUtf8();
auto ts = QDateTime::currentMSecsSinceEpoch();
struct chat_msg_submit* req = (struct chat_msg_submit*)

Loading…
Cancel
Save