call: P2P аудиозвонки — сигналинг, релей медиа, аудио-контур, headless/desktop/android
- src/call/: сигналинг (call.c, call_proto.h, ETCP_RT_ID_CALL 0x36) + релей медиа + headless аудио-сокет (call_headless.c, call_audio_bind) + Opus/jitter/тоны - chat: события CHAT_EVT_CALL_* (34–40), CHAT_EVT_DM_MESSAGES (41), DM-трамплины - chatgui: call window, miniaudio I/O, audio_frame_ring, test_audio_jitter - chatgui-android: CallScreen/IncomingCallDialog, аудио-роутинг, JNI, рингтон - tests: test_call, test_call_headless
This commit is contained in:
1 parent
0157f7f96b
commit
b11e77e4c0
78 files changed
+7053
-70
No files matched your search
@@ -17,6 +17,8 @@
|
||||
#include "voiceplayback.h"
|
||||
#include "audiorecorder.h"
|
||||
#include "voicemessageencoder.h"
|
||||
#include "callwindow.h"
|
||||
#include "call_audio_engine.h"
|
||||
|
||||
extern "C" {
|
||||
#include "../../lib/audio_compressor.h"
|
||||
@@ -39,10 +41,13 @@ extern "C" {
|
||||
#include <cmath>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRandomGenerator>
|
||||
#include <csignal>
|
||||
|
||||
extern "C" {
|
||||
#include "chat/chat_core.h"
|
||||
#include "call/call.h"
|
||||
#include "dm/dm_core.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
void chat_core_attachment_download_trampoline(void* arg);
|
||||
@@ -417,6 +422,38 @@ void MainWindow::setupBridgeCallbacks() {
|
||||
if (s_mainWindow->m_currentChannelId == chId)
|
||||
s_mainWindow->m_accountList->setChannel(chId);
|
||||
});
|
||||
|
||||
/* ── звонки ── */
|
||||
gui_bridge_set_call_incoming_cb([](const char* ch_id, int ch_id_len, uint64_t caller, uint64_t call_id) {
|
||||
if (!s_mainWindow) return;
|
||||
s_mainWindow->onCallIncoming(QString::fromUtf8(ch_id, ch_id_len), caller, call_id);
|
||||
});
|
||||
gui_bridge_set_call_ringing_cb([](uint64_t call_id) {
|
||||
if (s_mainWindow) s_mainWindow->onCallRinging(call_id);
|
||||
});
|
||||
gui_bridge_set_call_accepted_cb([](uint64_t call_id) {
|
||||
if (s_mainWindow) s_mainWindow->onCallAccepted(call_id);
|
||||
});
|
||||
gui_bridge_set_call_ended_cb([](uint64_t call_id, uint8_t reason) {
|
||||
if (s_mainWindow) s_mainWindow->onCallEnded(call_id, reason);
|
||||
});
|
||||
gui_bridge_set_call_declined_cb([](uint64_t call_id, uint8_t reason) {
|
||||
if (s_mainWindow) s_mainWindow->onCallEnded(call_id, reason);
|
||||
});
|
||||
|
||||
/* ── DM (p2p чат) ── */
|
||||
gui_bridge_set_dm_conv_updated_cb([](const char* conv_id, int conv_id_len) {
|
||||
if (s_mainWindow)
|
||||
s_mainWindow->onDmConvUpdated(QString::fromUtf8(conv_id, conv_id_len));
|
||||
});
|
||||
gui_bridge_set_dm_msg_received_cb([](const char* conv_id, int conv_id_len, uint64_t author) {
|
||||
if (s_mainWindow)
|
||||
s_mainWindow->onDmMsgReceived(QString::fromUtf8(conv_id, conv_id_len), author);
|
||||
});
|
||||
gui_bridge_set_dm_messages_cb([](const uint8_t* data, int len) {
|
||||
if (s_mainWindow)
|
||||
s_mainWindow->m_messageList->onDmMessages(data, len);
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::setupConnects() {
|
||||
@@ -425,6 +462,12 @@ void MainWindow::setupConnects() {
|
||||
saveLastChannelId(channelId);
|
||||
m_currentChannelId = channelId;
|
||||
m_messageList->loadChannel(channelId);
|
||||
if (channelId.startsWith(QStringLiteral("dm:"))) {
|
||||
/* DM: беседа уже создана/обслуживается dm_core */
|
||||
m_accountList->setChannel(QString());
|
||||
m_channelList->setChannelUnreadCount(channelId, 0);
|
||||
return;
|
||||
}
|
||||
m_accountList->setChannel(channelId);
|
||||
struct chat_create_auto_arg* arg = (struct chat_create_auto_arg*)u_calloc(1, sizeof(*arg));
|
||||
if (arg) {
|
||||
@@ -442,6 +485,8 @@ void MainWindow::setupConnects() {
|
||||
connect(m_channelList, &ChannelList::channelSettingsRequested, this, &MainWindow::showChannelSettings);
|
||||
connect(m_accountList, &AccountList::memberRenamed,
|
||||
m_messageList, &MessageList::reloadLocalNicks);
|
||||
connect(m_accountList, &AccountList::callRequested, this, &MainWindow::onCallRequested);
|
||||
connect(m_accountList, &AccountList::pmRequested, this, &MainWindow::onPmRequested);
|
||||
connect(m_messageList, &MessageList::readPositionChanged,
|
||||
this, [this](const QString& channelId, qint64 lastReadMsgId) {
|
||||
Q_UNUSED(lastReadMsgId);
|
||||
@@ -453,6 +498,107 @@ void MainWindow::setupConnects() {
|
||||
connect(m_messageList, &MessageList::openImageRequested, this, &MainWindow::openImage);
|
||||
}
|
||||
|
||||
/* ── звонки ── */
|
||||
|
||||
void MainWindow::onCallRequested(const QString& chId, quint64 nodeId, const QString& name) {
|
||||
quint64 callId = QRandomGenerator::global()->generate64();
|
||||
if (!callId) callId = 1;
|
||||
|
||||
struct call_start_arg* a = (struct call_start_arg*)u_malloc(sizeof(*a));
|
||||
if (!a) return;
|
||||
memset(a, 0, sizeof(*a));
|
||||
a->inst = gui_bridge_get_inst();
|
||||
strncpy(a->ch_id, chId.toUtf8().constData(), sizeof(a->ch_id) - 1);
|
||||
a->peer_node_id = nodeId;
|
||||
a->call_id = callId;
|
||||
gui_bridge_post_uasync_fn(call_start_trampoline, a);
|
||||
|
||||
CallWindow* w = new CallWindow(this);
|
||||
w->setCallId(callId);
|
||||
w->setPeerName(name);
|
||||
w->setState(CallWindow::Outgoing);
|
||||
connect(w, &CallWindow::closed, this, &MainWindow::closeCallWindow);
|
||||
m_activeCalls.insert(callId, w);
|
||||
w->show();
|
||||
}
|
||||
|
||||
void MainWindow::onCallIncoming(const QString& chId, quint64 caller, quint64 callId) {
|
||||
Q_UNUSED(chId);
|
||||
if (m_activeCalls.contains(callId)) return;
|
||||
QString name = m_db->getDisplayName(caller);
|
||||
if (name.isEmpty()) name = QStringLiteral("0x%1").arg(caller, 16, 16, QChar('0'));
|
||||
|
||||
CallWindow* w = new CallWindow(this);
|
||||
w->setCallId(callId);
|
||||
w->setPeerName(name);
|
||||
w->setState(CallWindow::Incoming);
|
||||
connect(w, &CallWindow::closed, this, &MainWindow::closeCallWindow);
|
||||
m_activeCalls.insert(callId, w);
|
||||
w->show();
|
||||
w->raise();
|
||||
w->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::onCallRinging(quint64 callId) {
|
||||
/* caller слышит гудок — окно уже показано в Outgoing */
|
||||
Q_UNUSED(callId);
|
||||
}
|
||||
|
||||
void MainWindow::onCallAccepted(quint64 callId) {
|
||||
CallWindow* w = m_activeCalls.value(callId, nullptr);
|
||||
if (w) w->setState(CallWindow::Active);
|
||||
if (m_db)
|
||||
CallAudioEngine::instance()->setPlaybackDeviceIndex(m_db->getUiStateInt("audio_output_device", -1));
|
||||
CallAudioEngine::instance()->start(callId);
|
||||
}
|
||||
|
||||
void MainWindow::onCallEnded(quint64 callId, uint8_t reason) {
|
||||
Q_UNUSED(reason);
|
||||
/* тон завершения (~450мс) → авто-teardown аудио-движка */
|
||||
if (CallAudioEngine::instance()->callId() == callId)
|
||||
CallAudioEngine::instance()->beginEnd(callId);
|
||||
closeCallWindow(callId);
|
||||
}
|
||||
|
||||
void MainWindow::closeCallWindow(quint64 callId) {
|
||||
CallWindow* w = m_activeCalls.take(callId);
|
||||
if (!w) return;
|
||||
w->disconnect(this);
|
||||
w->deleteLater();
|
||||
}
|
||||
|
||||
/* ── DM (p2p чат) ── */
|
||||
|
||||
void MainWindow::onPmRequested(const QString& chId, quint64 nodeId, const QString& name) {
|
||||
struct dm_start_req* r = (struct dm_start_req*)u_malloc(sizeof(*r));
|
||||
if (!r) return;
|
||||
memset(r, 0, sizeof(*r));
|
||||
r->inst = gui_bridge_get_inst();
|
||||
r->peer_node_id = nodeId;
|
||||
strncpy(r->source_ch_id, chId.toUtf8().constData(), sizeof(r->source_ch_id) - 1);
|
||||
strncpy(r->peer_name, name.toUtf8().constData(), sizeof(r->peer_name) - 1);
|
||||
gui_bridge_post_uasync_fn(dm_start_trampoline, r);
|
||||
/* открытие беседы произойдёт по CHAT_EVT_DM_CONV_UPDATED (onDmConvUpdated) */
|
||||
}
|
||||
|
||||
void MainWindow::onDmConvUpdated(const QString& convId) {
|
||||
if (!m_channelList) return;
|
||||
QString key = QStringLiteral("dm:") + convId;
|
||||
m_channelList->loadChannels();
|
||||
m_channelList->selectChannel(key);
|
||||
}
|
||||
|
||||
void MainWindow::onDmMsgReceived(const QString& convId, quint64 author) {
|
||||
QString key = QStringLiteral("dm:") + convId;
|
||||
if (m_currentChannelId == key) {
|
||||
m_messageList->refresh();
|
||||
return;
|
||||
}
|
||||
if (author == m_db->myNodeId()) return;
|
||||
if (m_channelList)
|
||||
m_channelList->setChannelUnreadCount(key, m_channelList->getChannelUnreadCount(key) + 1);
|
||||
}
|
||||
|
||||
void MainWindow::setupTray() {
|
||||
m_trayIcon = new QSystemTrayIcon(this);
|
||||
m_trayIcon->setIcon(style()->standardIcon(QStyle::SP_ComputerIcon));
|
||||
|
||||
Reference in new issue
Block a user