Browse Source

Add live call video controls and display to the desktop call window

stream-multiview
evgeny 7 days ago
parent
commit
af35d03399
  1. 4
      tools/chatgui/CMakeLists.txt
  2. 57
      tools/chatgui/src/callwindow.cpp
  3. 12
      tools/chatgui/src/callwindow.h

4
tools/chatgui/CMakeLists.txt

@ -139,7 +139,7 @@ if(PkgConfig_FOUND)
endif()
# Общий sender остаётся в libutun; Qt захватывает видео, FFmpeg/Opus работают в publisher workers.
add_library(chatgui_live_video STATIC src/live_video_encoder.cpp src/call_video_encoder.cpp)
add_library(chatgui_live_video STATIC src/live_video_encoder.cpp src/call_video_encoder.cpp src/call_video_decoder.cpp)
target_link_libraries(chatgui_live_video PUBLIC ${QT_LIBS} utun PRIVATE ${FFMPEG_TARGET} ${FFMPEG_LIBRARIES})
target_include_directories(chatgui_live_video PRIVATE ${FFMPEG_INCLUDE_DIRS})
if(FFMPEG_FOUND)
@ -150,7 +150,7 @@ if(FFMPEG_FOUND)
add_test(NAME test_call_video_encoder COMMAND test_call_video_encoder)
set_tests_properties(test_call_video_encoder PROPERTIES TIMEOUT 120)
endif()
add_library(chatgui_stream_publish STATIC src/stream_publish_engine.cpp src/stream_publish_window.cpp)
add_library(chatgui_stream_publish STATIC src/stream_publish_engine.cpp src/stream_publish_window.cpp src/call_video_engine.cpp)
target_link_libraries(chatgui_stream_publish PUBLIC chatgui_live_video)
target_include_directories(chatgui_stream_publish PRIVATE ${CMAKE_SOURCE_DIR}/../../lib/libopus/include)
set(GUI_CAPTURE_ENABLED 0)

57
tools/chatgui/src/callwindow.cpp

@ -2,6 +2,8 @@
#include "callwindow.h"
#include "../transport/gui_bridge.h"
#include "call_audio_engine.h"
#include "call_video_engine.h"
#include "../../../src/call/call_video_port.h"
#include "sound_manager.h"
#include <QVBoxLayout>
@ -9,6 +11,8 @@
#include <QLabel>
#include <QPushButton>
#include <QComboBox>
#include <QCheckBox>
#include <QSpinBox>
#include <QTimer>
#include <QDateTime>
#include <QCloseEvent>
@ -138,7 +142,8 @@ static QWidget* wrapWithCaption(QWidget* btn, const QString& caption, QLabel** c
CallWindow::CallWindow(QWidget* parent)
: QWidget(parent, Qt::Window) {
setWindowTitle(QCoreApplication::translate("ChatGui", "Call"));
setFixedSize(360, 480);
resize(640, 800);
setMinimumSize(400, 600);
setAttribute(Qt::WA_DeleteOnClose, false);
m_avatarLabel = new QLabel(this);
@ -193,6 +198,45 @@ CallWindow::CallWindow(QWidget* parent)
lay->addWidget(m_connectionLabel);
lay->addWidget(m_bgpPathLabel);
lay->addWidget(m_statsLabel);
m_video = new CallVideoEngine(this);
m_videoImage = new QLabel(this); m_videoImage->setMinimumHeight(180); m_videoImage->setAlignment(Qt::AlignCenter);
lay->addWidget(m_videoImage, 1);
m_videoStatus = new QLabel(this); m_videoStatus->setWordWrap(true); lay->addWidget(m_videoStatus);
m_videoControls = new QWidget(this); auto* videoLayout = new QVBoxLayout(m_videoControls);
videoLayout->setContentsMargins(0, 0, 0, 0);
m_videoEnabled = new QCheckBox(tr("Send video"), this); videoLayout->addWidget(m_videoEnabled);
auto* modes = new QHBoxLayout();
m_camera = new QComboBox(this);
for (const auto& camera : CallVideoEngine::cameras()) m_camera->addItem(camera.first, camera.second);
m_resolution = new QComboBox(this); m_resolution->addItem("640×480", QSize(640, 480)); m_resolution->addItem("1280×720", QSize(1280, 720));
m_codec = new QComboBox(this); m_codec->addItem(tr("Auto (H.265 → H.264)"), 0); m_codec->addItem("H.265", 2); m_codec->addItem("H.264", 1);
modes->addWidget(m_camera, 1); modes->addWidget(m_resolution); modes->addWidget(m_codec); videoLayout->addLayout(modes);
auto* rates = new QHBoxLayout(); rates->addWidget(new QLabel(tr("Bitrate range, kbit/s:"), this));
m_videoMin = new QSpinBox(this); m_videoMax = new QSpinBox(this);
for (auto* spin : {m_videoMin, m_videoMax}) { spin->setRange(300, 4000); spin->setSingleStep(100); rates->addWidget(spin); }
m_videoMin->setValue(300); m_videoMax->setValue(4000); videoLayout->addLayout(rates); lay->addWidget(m_videoControls);
connect(m_videoEnabled, &QCheckBox::toggled, this, [this] { applyVideo(); });
for (auto* combo : {m_camera, m_resolution, m_codec})
connect(combo, QOverload<int>::of(&QComboBox::activated), this, [this] { applyVideo(); });
auto updateRange = [this] {
if (m_videoMin->value() > m_videoMax->value()) m_videoMax->setValue(m_videoMin->value());
if (m_videoCall) call_video_port_range(m_videoCall, m_videoMin->value() * 1000, m_videoMax->value() * 1000);
};
connect(m_videoMin, &QSpinBox::editingFinished, this, updateRange);
connect(m_videoMax, &QSpinBox::editingFinished, this, updateRange);
connect(m_video, &CallVideoEngine::statusChanged, m_videoStatus, &QLabel::setText);
connect(m_video, &CallVideoEngine::imageChanged, this, [this](const QImage& image) {
m_videoImage->setPixmap(image.isNull() ? QPixmap() : QPixmap::fromImage(image).scaled(m_videoImage->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
});
auto* videoTimer = new QTimer(this); videoTimer->setInterval(100);
connect(videoTimer, &QTimer::timeout, this, [this] {
if (m_state != Active || !CallAudioEngine::instance()->isActive() || CallAudioEngine::instance()->callId() != m_callId) return;
if (m_videoCall != m_callId) { m_videoCall = m_callId; m_videoRangeReady = false; m_video->start(m_callId); applyVideo(); }
struct call_video_port_status status{};
if (!m_videoRangeReady && call_video_port_status(m_callId, &status) == 0 && status.minimum_bps) {
m_videoMin->setValue(status.minimum_bps / 1000); m_videoMax->setValue(status.maximum_bps / 1000); m_videoRangeReady = true;
}
}); videoTimer->start();
lay->addStretch(1);
m_acceptCell = wrapWithCaption(m_acceptBtn, QCoreApplication::translate("ChatGui", "Accept"));
@ -269,6 +313,7 @@ void CallWindow::setState(State s) {
m_muteBtn->setChecked(false);
break;
case Ended:
m_video->stop(); m_videoCall = 0;
setConnectionType(0, 0);
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Ended"));
m_durationTimer->stop();
@ -300,6 +345,15 @@ void CallWindow::updateButtons() {
m_hangupCell->setVisible(m_state == Outgoing || m_state == Ringing || m_state == Active);
m_muteCell->setVisible(m_state == Active);
m_deviceCombo->setVisible(m_state == Active);
m_videoControls->setVisible(m_state == Active);
m_videoImage->setVisible(m_state == Active);
m_videoStatus->setVisible(m_state == Active);
}
void CallWindow::applyVideo() {
if (!m_videoCall) return;
QSize size = m_resolution->currentData().toSize();
m_video->setVideo(m_videoEnabled->isChecked(), m_camera->currentData().toByteArray(), size.width(), size.height(), m_codec->currentData().toInt());
}
void CallWindow::postCallFn(void (*fn)(void*), uint64_t callId) {
@ -338,6 +392,7 @@ void CallWindow::onDeviceChanged(int index) {
}
void CallWindow::closeEvent(QCloseEvent* event) {
m_video->stop(); m_videoCall = 0;
if (m_state == Outgoing || m_state == Ringing || m_state == Active)
postCallFn(call_hangup_trampoline, m_callId);
emit closed(m_callId);

12
tools/chatgui/src/callwindow.h

@ -7,6 +7,9 @@ class QLabel;
class QPushButton;
class QComboBox;
class QTimer;
class QCheckBox;
class QSpinBox;
class CallVideoEngine;
/* Окно звонка: состояния Outgoing (пытаемся связаться), Ringing (звоним),
* Incoming (принять/отклонить), Active (идёт разговор, таймер), Ended (завершён). */
@ -46,6 +49,15 @@ private:
void updateButtons();
void populateDeviceCombo();
void postCallFn(void (*fn)(void*), uint64_t callId);
void applyVideo();
CallVideoEngine* m_video;
QCheckBox* m_videoEnabled;
QComboBox *m_camera, *m_resolution, *m_codec;
QSpinBox *m_videoMin, *m_videoMax;
QLabel *m_videoImage, *m_videoStatus;
QWidget* m_videoControls;
quint64 m_videoCall = 0;
bool m_videoRangeReady = false;
quint64 m_callId = 0;
State m_state = Outgoing;

Loading…
Cancel
Save