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); m_voiceFilePath = QString("%1/%2_%3_%4.%5").arg(mediaPath, blk.dt, blk.basename, blk.suffix, blk.ext);
const auto& wf = m_recorder->waveformLevels(); const auto& wf = m_recorder->waveformLevels();
QString wfStr; QString wfStr = "wf=";
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int N = (int)wf.size(); int N = (int)wf.size();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
int s = i * N / 100, e = (i + 1) * N / 100; int s = i * N / 100, e = (i + 1) * N / 100;
@ -260,9 +261,12 @@ void InputBar::onPttReleased() {
float sum = 0; int cnt = 0; float sum = 0; int cnt = 0;
for (int j = s; j < e; j++) { sum += wf[j]; cnt++; } for (int j = s; j < e; j++) { sum += wf[j]; cnt++; }
float v = cnt > 0 ? sum / cnt : 0; float v = cnt > 0 ? sum / cnt : 0;
if (i > 0) wfStr += ','; float db = v > 0.00001f ? 20.0f * log10f(v) : -100.0f;
wfStr += QString::number(v, 'f', 3); 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", GUI_INFO("InputBar: voice message ready: dt=%s stem=%s blocks=%d dur=%.1fs",
qPrintable(blk.dt), qPrintable(blk.basename), blk.numBlocks, durationSec); qPrintable(blk.dt), qPrintable(blk.basename), blk.numBlocks, durationSec);
emit sendVoiceMessage(blk.dt, blk.basename, blk.suffix, blk.ext, 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; float barW = (float)wfR.width() / wfN;
int ph = wfR.height() - 3; int ph = wfR.height() - 3;
for (int i = 0; i < wfN; i++) { for (int i = 0; i < wfN; i++) {
float v = wf[i].toFloat(); float logV = wf[i].toFloat(); /* already 0..1 normalized */
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;
int barH = 2 + (int)(logV * (ph - 2)); int barH = 2 + (int)(logV * (ph - 2));
int x = wfR.left() + (int)(i * barW); int x = wfR.left() + (int)(i * barW);
int y = wfR.top() + ph - barH; int y = wfR.top() + ph - barH;
@ -724,8 +721,8 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
QColor idle(0x70, 0x70, 0x78, 130); QColor idle(0x70, 0x70, 0x78, 130);
painter->setBrush((isPlaying || isPaused) ? (before ? played : unplayed) : idle); painter->setBrush((isPlaying || isPaused) ? (before ? played : unplayed) : idle);
painter->drawRect(QRect(x, y, bw, barH)); 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", if (i == 0) GUI_DEBUG("voice paint: wfN=%d wfW=%d logV=%.2f barH=%d",
wfN, wfR.width(), v, db, logV, barH); wfN, wfR.width(), logV, barH);
} }
} }

72
tools/chatgui/src/messagelist.cpp

@ -13,6 +13,8 @@
#include <QPainter> #include <QPainter>
#include <QPixmap> #include <QPixmap>
#include <QDateTime> #include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFile> #include <QFile>
#include <QScrollBar> #include <QScrollBar>
#include <QApplication> #include <QApplication>
@ -95,23 +97,26 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
if (ct == "audio/opus") { if (ct == "audio/opus") {
QString raw = QString::fromUtf8(data); QString raw = QString::fromUtf8(data);
int pipe1 = raw.indexOf('|'); if (raw.startsWith('{')) return;
if (pipe1 < 0 && raw.startsWith('{')) return;
int pipe2 = raw.indexOf('|', pipe1 + 1); /* unified format: wf={base64};dur={ms};|{file_size}|{block_size}|{num_blocks}|{sigs} */
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; } }
}
QString wfData; QString wfData;
if (afterWf >= 0) { int durMs = 0;
wfData = rest.left(afterWf);
QString meta = rest.mid(afterWf + 1); 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('|'); QStringList parts = meta.split('|');
if (parts.size() >= 4) { if (parts.size() >= 4) {
item->setData(parts[0].toULongLong(), MsgVoiceFileSizeRole); 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[2].toUInt(), MsgVoiceNumBlocksRole);
item->setData(parts[3], MsgVoiceSigsRole); item->setData(parts[3], MsgVoiceSigsRole);
} }
} else {
wfData = rest;
} }
if (!dur.isEmpty()) item->setData(dur + "s", MsgVoiceDurationRole); /* file path from local_attrs */
if (!fname.isEmpty()) { QJsonObject la = QJsonDocument::fromJson(localAttrs).object();
QString fullPath = mediaDirBase + "/" + channelId + "/" + fname; QString fp = la.value("fp").toString();
if (!fp.isEmpty()) {
QString fullPath = mediaDirBase + "/" + channelId + "/" + fp;
item->setData(fullPath, MsgVoiceFileRole); 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()) { if (!wfData.isEmpty()) {
QList<QVariant> wf; QList<QVariant> wf;
for (const auto& s : wfData.split(',')) for (int i = 0; i < wfData.size(); i++) {
wf.append(s.toDouble()); 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); 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") { if (ct == "application/octet-stream") {
@ -240,9 +252,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
int durationMs, const QString& wfStr) { int durationMs, const QString& wfStr) {
if (m_currentChannelId.isEmpty()) return; if (m_currentChannelId.isEmpty()) return;
QString displayName = basename + "." + ext; QByteArray textData = wfStr.toUtf8();
QByteArray textData = (displayName + "|" +
QString::number(durationMs / 1000.0, 'f', 1) + "|" + wfStr).toUtf8();
auto ts = QDateTime::currentMSecsSinceEpoch(); auto ts = QDateTime::currentMSecsSinceEpoch();
struct chat_msg_submit* req = (struct chat_msg_submit*) struct chat_msg_submit* req = (struct chat_msg_submit*)

Loading…
Cancel
Save