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
@@ -0,0 +1,179 @@
|
||||
#include "callwindow.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include "call_audio_engine.h"
|
||||
#include "sound_manager.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QComboBox>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
#include <QCloseEvent>
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
#include "call/call.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
}
|
||||
|
||||
CallWindow::CallWindow(QWidget* parent)
|
||||
: QWidget(parent, Qt::Window) {
|
||||
setWindowTitle(QStringLiteral("Звонок"));
|
||||
setFixedSize(320, 300);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
m_peerLabel = new QLabel(this);
|
||||
m_peerLabel->setAlignment(Qt::AlignCenter);
|
||||
m_statusLabel = new QLabel(this);
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_durationLabel = new QLabel(QStringLiteral("00:00"), this);
|
||||
m_durationLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
m_acceptBtn = new QPushButton(QStringLiteral("Принять"), this);
|
||||
m_declineBtn = new QPushButton(QStringLiteral("Отклонить"), this);
|
||||
m_hangupBtn = new QPushButton(QStringLiteral("Завершить"), this);
|
||||
|
||||
m_muteBtn = new QPushButton(QStringLiteral("Mute"), this);
|
||||
m_muteBtn->setCheckable(true);
|
||||
m_muteBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 4px 12px; font-size: 12px; background: palette(button); }"
|
||||
"QPushButton:checked { background: #c0392b; color: white; }");
|
||||
|
||||
m_deviceCombo = new QComboBox(this);
|
||||
m_deviceCombo->setStyleSheet(
|
||||
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
|
||||
|
||||
QVBoxLayout* lay = new QVBoxLayout(this);
|
||||
lay->addWidget(m_peerLabel);
|
||||
lay->addWidget(m_statusLabel);
|
||||
lay->addWidget(m_durationLabel);
|
||||
QHBoxLayout* btn = new QHBoxLayout();
|
||||
btn->addWidget(m_acceptBtn);
|
||||
btn->addWidget(m_declineBtn);
|
||||
btn->addWidget(m_hangupBtn);
|
||||
lay->addLayout(btn);
|
||||
lay->addWidget(m_muteBtn);
|
||||
lay->addWidget(m_deviceCombo);
|
||||
|
||||
connect(m_acceptBtn, &QPushButton::clicked, this, &CallWindow::onAccept);
|
||||
connect(m_declineBtn, &QPushButton::clicked, this, &CallWindow::onDecline);
|
||||
connect(m_hangupBtn, &QPushButton::clicked, this, &CallWindow::onHangup);
|
||||
connect(m_muteBtn, &QPushButton::toggled, this, &CallWindow::onMuteToggled);
|
||||
connect(m_deviceCombo, QOverload<int>::of(&QComboBox::activated), this, &CallWindow::onDeviceChanged);
|
||||
|
||||
m_durationTimer = new QTimer(this);
|
||||
m_durationTimer->setInterval(1000);
|
||||
connect(m_durationTimer, &QTimer::timeout, this, &CallWindow::updateDuration);
|
||||
|
||||
setState(Outgoing);
|
||||
}
|
||||
|
||||
CallWindow::~CallWindow() = default;
|
||||
|
||||
void CallWindow::setPeerName(const QString& name) {
|
||||
m_peerLabel->setText(name);
|
||||
}
|
||||
|
||||
void CallWindow::setState(State s) {
|
||||
m_state = s;
|
||||
switch (s) {
|
||||
case Outgoing:
|
||||
m_statusLabel->setText(QStringLiteral("Звоним…"));
|
||||
m_durationLabel->hide();
|
||||
break;
|
||||
case Incoming:
|
||||
m_statusLabel->setText(QStringLiteral("Входящий звонок…"));
|
||||
m_durationLabel->hide();
|
||||
break;
|
||||
case Active:
|
||||
m_statusLabel->setText(QStringLiteral("Разговор"));
|
||||
m_durationLabel->setText(QStringLiteral("00:00"));
|
||||
m_durationLabel->show();
|
||||
m_startMs = QDateTime::currentMSecsSinceEpoch();
|
||||
m_durationTimer->start();
|
||||
populateDeviceCombo();
|
||||
m_muteBtn->setChecked(false);
|
||||
break;
|
||||
case Ended:
|
||||
m_statusLabel->setText(QStringLiteral("Завершён"));
|
||||
m_durationTimer->stop();
|
||||
break;
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void CallWindow::populateDeviceCombo() {
|
||||
m_deviceCombo->blockSignals(true);
|
||||
m_deviceCombo->clear();
|
||||
m_deviceCombo->addItem(QStringLiteral("Default (auto)"), -1);
|
||||
const auto devs = SoundManager::enumeratePlaybackDevices();
|
||||
for (int i = 0; i < devs.size(); i++) {
|
||||
QString label = devs[i].name;
|
||||
if (devs[i].isDefault) label += QStringLiteral(" (default)");
|
||||
m_deviceCombo->addItem(label, i);
|
||||
}
|
||||
int cur = CallAudioEngine::instance()->playbackDeviceIndex();
|
||||
for (int i = 0; i < m_deviceCombo->count(); i++) {
|
||||
if (m_deviceCombo->itemData(i).toInt() == cur) { m_deviceCombo->setCurrentIndex(i); break; }
|
||||
}
|
||||
m_deviceCombo->blockSignals(false);
|
||||
}
|
||||
|
||||
void CallWindow::updateButtons() {
|
||||
m_acceptBtn->setVisible(m_state == Incoming);
|
||||
m_declineBtn->setVisible(m_state == Incoming);
|
||||
m_hangupBtn->setVisible(m_state == Outgoing || m_state == Active);
|
||||
m_muteBtn->setVisible(m_state == Active);
|
||||
m_deviceCombo->setVisible(m_state == Active);
|
||||
}
|
||||
|
||||
void CallWindow::postCallFn(void (*fn)(void*), uint64_t callId) {
|
||||
struct call_id_arg* a = (struct call_id_arg*)u_malloc(sizeof(*a));
|
||||
if (!a) return;
|
||||
a->inst = gui_bridge_get_inst();
|
||||
a->call_id = callId;
|
||||
gui_bridge_post_uasync_fn(fn, a);
|
||||
}
|
||||
|
||||
void CallWindow::onAccept() {
|
||||
setState(Active);
|
||||
postCallFn(call_accept_trampoline, m_callId);
|
||||
}
|
||||
|
||||
void CallWindow::onDecline() {
|
||||
postCallFn(call_decline_trampoline, m_callId);
|
||||
}
|
||||
|
||||
void CallWindow::onHangup() {
|
||||
postCallFn(call_hangup_trampoline, m_callId);
|
||||
}
|
||||
|
||||
void CallWindow::onMuteToggled(bool checked) {
|
||||
CallAudioEngine::instance()->setMuted(checked);
|
||||
m_muteBtn->setText(checked ? QStringLiteral("Вкл. микрофон") : QStringLiteral("Mute"));
|
||||
}
|
||||
|
||||
void CallWindow::onDeviceChanged(int index) {
|
||||
int devIdx = m_deviceCombo->itemData(index).toInt();
|
||||
CallAudioEngine::instance()->setPlaybackDeviceIndex(devIdx);
|
||||
}
|
||||
|
||||
void CallWindow::closeEvent(QCloseEvent* event) {
|
||||
if (m_state == Active) postCallFn(call_hangup_trampoline, m_callId);
|
||||
emit closed(m_callId);
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void CallWindow::updateDuration() {
|
||||
if (m_state != Active) return;
|
||||
qint64 ms = QDateTime::currentMSecsSinceEpoch() - m_startMs;
|
||||
qint64 s = ms / 1000;
|
||||
m_durationLabel->setText(QStringLiteral("%1:%2")
|
||||
.arg(s / 60, 2, 10, QChar('0'))
|
||||
.arg(s % 60, 2, 10, QChar('0')));
|
||||
}
|
||||
Reference in new issue
Block a user