feat: integrate Opus codec into lib/libopus + voice messages in chatgui
- Add lib/libopus/ (opus v1.6.1 embedded source: CELT 18 .c, SILK 77 .c, SILK float 28 .c, OPUS core 14 .c) - Add lib/opus_codec.h/.c (wrapper: encoder, decoder, raw PCM int16) - Add tests/test_opus_codec.c (7 tests: silence, sine, compression, PLC, null, defaults, multi-frame) - chatgui: AudioRecorder (miniaudio capture, 48kHz mono) - chatgui: VoiceEncoder (PCM→Opus file with custom container) - chatgui: VoicePlayer (Opus→PCM playback via SoundManager) - chatgui: PTT button in InputBar, voice message send/receive in MessageList - chatgui: AudioDeviceSettingsPage (device selection, persisted in ui_state) - chatgui: MessageDelegate voice message rendering (play button + duration) - Update autotools: configure.ac, Makefile.am, src/Makefile.am, tests/Makefile.am - Update CMake: chatgui CMakeLists.txt, libutun CMakeLists.txt (link libopus_internal.a)
This commit is contained in:
1 parent
4a23593020
commit
4fde0b0325
260 files changed
+72665
-16
No files matched your search
@@ -16,6 +16,7 @@
|
||||
#include <QScrollBar>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
@@ -76,7 +77,7 @@ static void setMsg(QStandardItem *item,
|
||||
item->setData(reactions, MsgReactionsRole);
|
||||
}
|
||||
|
||||
MessageList::MessageList(DbManager* db, QWidget *parent)
|
||||
MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_view(new ChatView(this))
|
||||
, m_model(new QStandardItemModel(this))
|
||||
@@ -119,7 +120,7 @@ MessageList::MessageList(DbManager* db, QWidget *parent)
|
||||
m_emojiPanel = new EmojiPanel(this);
|
||||
layout->addWidget(m_emojiPanel);
|
||||
|
||||
m_inputBar = new InputBar(m_emojiPanel, this);
|
||||
m_inputBar = new InputBar(m_emojiPanel, recorder, this);
|
||||
layout->addWidget(m_inputBar);
|
||||
|
||||
connect(m_inputBar, &InputBar::sendMessage, this, [this](const QString &text) {
|
||||
@@ -144,6 +145,33 @@ MessageList::MessageList(DbManager* db, QWidget *parent)
|
||||
gui_bridge_post_uasync_fn(chat_core_submit_trampoline, req);
|
||||
});
|
||||
|
||||
connect(m_inputBar, &InputBar::sendVoiceMessage, this, [this](const QString &filePath, int durationMs) {
|
||||
if (m_currentChannelId.isEmpty()) return;
|
||||
|
||||
QFileInfo fi(filePath);
|
||||
QString fileName = fi.fileName();
|
||||
QString jsonData = QString("{\"n\":%1,\"ch\":\"%2\",\"ct\":\"audio/opus\",\"d\":\"%3\",\"dur\":%4}")
|
||||
.arg(m_db ? m_db->myNodeId() : 0)
|
||||
.arg(m_currentChannelId)
|
||||
.arg(fileName)
|
||||
.arg(durationMs / 1000.0, 0, 'f', 1);
|
||||
|
||||
QByteArray textData = jsonData.toUtf8();
|
||||
auto ts = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
struct chat_msg_submit* req = (struct chat_msg_submit*)
|
||||
u_malloc(sizeof(struct chat_msg_submit) + textData.size());
|
||||
memset(req, 0, sizeof(*req));
|
||||
snprintf(req->channel_id, sizeof(req->channel_id), "%s",
|
||||
m_currentChannelId.toUtf8().constData());
|
||||
snprintf(req->content_type, sizeof(req->content_type), "audio/opus");
|
||||
req->data = (uint8_t*)(req + 1);
|
||||
req->data_len = (uint32_t)textData.size();
|
||||
memcpy(req->data, textData.constData(), textData.size());
|
||||
req->timestamp = (uint64_t)ts;
|
||||
gui_bridge_post_uasync_fn(chat_core_submit_trampoline, req);
|
||||
});
|
||||
|
||||
connect(AnimTimer::instance(), &AnimTimer::ticked, this, [this]() {
|
||||
m_view->viewport()->update();
|
||||
});
|
||||
@@ -224,6 +252,18 @@ void MessageList::loadChannel(const QString& channelId) {
|
||||
item->setData(m.id, MsgIdRole);
|
||||
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole);
|
||||
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
|
||||
item->setData(m.contentType, MsgContentTypeRole);
|
||||
if (m.contentType == "audio/opus") {
|
||||
/* JSON data contains {"dur":X.X} — parse duration */
|
||||
QByteArray d = m.data;
|
||||
int durIdx = d.indexOf("\"dur\":");
|
||||
if (durIdx >= 0) {
|
||||
int durEnd = d.indexOf(',', durIdx);
|
||||
if (durEnd < 0) durEnd = d.indexOf('}', durIdx);
|
||||
QByteArray durStr = d.mid(durIdx + 6, durEnd - durIdx - 6);
|
||||
item->setData(QString::fromUtf8(durStr) + "s", MsgVoiceDurationRole);
|
||||
}
|
||||
}
|
||||
m_model->appendRow(item);
|
||||
}
|
||||
|
||||
@@ -287,6 +327,17 @@ void MessageList::refresh() {
|
||||
item->setData(m.id, MsgIdRole);
|
||||
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole);
|
||||
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
|
||||
item->setData(m.contentType, MsgContentTypeRole);
|
||||
if (m.contentType == "audio/opus") {
|
||||
QByteArray d = m.data;
|
||||
int durIdx = d.indexOf("\"dur\":");
|
||||
if (durIdx >= 0) {
|
||||
int durEnd = d.indexOf(',', durIdx);
|
||||
if (durEnd < 0) durEnd = d.indexOf('}', durIdx);
|
||||
QByteArray durStr = d.mid(durIdx + 6, durEnd - durIdx - 6);
|
||||
item->setData(QString::fromUtf8(durStr) + "s", MsgVoiceDurationRole);
|
||||
}
|
||||
}
|
||||
m_model->appendRow(item);
|
||||
while (m_model->rowCount() > 100)
|
||||
m_model->removeRow(0);
|
||||
|
||||
Reference in new issue
Block a user