diff options
| author | Anton <anton@picapica.im> | 2026-08-13 01:28:32 +0400 |
|---|---|---|
| committer | Anton <anton@picapica.im> | 2026-08-13 01:28:32 +0400 |
| commit | d10deee6b3143fffd0d4d55b87bf6167c34dd459 (patch) | |
| tree | d10fc58c4d8382b37046062830010d305094ee69 /pica-client/dialogs/settingsdialog.cpp | |
| parent | 07697978227d9d938a2ce2b72010d3b9cebea041 (diff) | |
| download | pica-pica-d10deee6b3143fffd0d4d55b87bf6167c34dd459.tar.gz pica-pica-d10deee6b3143fffd0d4d55b87bf6167c34dd459.zip | |
pica-client: video call
Diffstat (limited to 'pica-client/dialogs/settingsdialog.cpp')
| -rw-r--r-- | pica-client/dialogs/settingsdialog.cpp | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/pica-client/dialogs/settingsdialog.cpp b/pica-client/dialogs/settingsdialog.cpp index 86faba6..536d141 100644 --- a/pica-client/dialogs/settingsdialog.cpp +++ b/pica-client/dialogs/settingsdialog.cpp @@ -155,12 +155,40 @@ SettingsDialog::SettingsDialog(QWidget *parent) : fillVideoDevices(); videoDevRefresh = new QPushButton(tr("Refresh 🔄"), this); connect(videoDevRefresh, SIGNAL(clicked()), this, SLOT(fillVideoDevices())); + + btVideoTest = new QPushButton(tr("Test 📷"), this); + connect(btVideoTest, SIGNAL(clicked()), this, SLOT(toggleVideoTest())); + + videoPreview = new QLabel(this); + videoPreview->setAlignment(Qt::AlignCenter); + videoPreview->setMinimumSize(320, 240); + videoPreview->setFrameShape(QFrame::StyledPanel); + videodevLayout->addWidget(videoDev); videodevLayout->addWidget(videoDevRefresh); + videodevLayout->addWidget(btVideoTest); + videodevLayout->addWidget(videoPreview); videodevLayout->addStretch(1); videodevtab->setLayout(videodevLayout); + // Capture+encode and decode ends of the test pipeline, each blocking its + // own thread while running, exactly as they do during a call. They stay + // idle until the Test button is pressed. + videoTestRunning = false; + + testCam = new VideoDevice(); + testCam->moveToThread(&testCamThread); + connect(testCam, SIGNAL(packetReady(QByteArray,bool)), this, SLOT(videoTestFragment(QByteArray,bool))); + connect(testCam, SIGNAL(errorOccurred(QString)), this, SLOT(videoTestError(QString))); + testCamThread.start(); + + testDecoder = new VideoDevice(); + testDecoder->moveToThread(&testDecoderThread); + connect(testDecoder, SIGNAL(frameReady(QImage)), this, SLOT(videoTestFrame(QImage))); + connect(testDecoder, SIGNAL(errorOccurred(QString)), this, SLOT(videoTestError(QString))); + testDecoderThread.start(); + tabW->addTab(directc2ctab, tr("Direct Connections")); tabW->addTab(multilogintab, tr("Multiple logins")); tabW->addTab(audiodevtab, tr("Audio Devices")); @@ -189,6 +217,106 @@ SettingsDialog::SettingsDialog(QWidget *parent) : setWindowTitle(tr("Pica Pica Messenger Settings")); } +SettingsDialog::~SettingsDialog() +{ + // Stops the loops first - the threads cannot finish while Capture()/Play() + // are still running inside them. + testCam->Close(); + testDecoder->Close(); + + testCamThread.quit(); + testCamThread.wait(); + delete testCam; + + testDecoderThread.quit(); + testDecoderThread.wait(); + delete testDecoder; +} + +void SettingsDialog::toggleVideoTest() +{ + if (videoTestRunning) + { + stopVideoTest(); + return; + } + + QString dev = videoDev->itemData(videoDev->currentIndex()).toString(); + + if (dev.isEmpty()) + { + videoPreview->setText(tr("No camera selected")); + return; + } + + testAssembler.reset(); + videoTestRunning = true; + btVideoTest->setText(tr("Stop ⏹")); + videoPreview->setText(tr("Starting...")); + + QMetaObject::invokeMethod(testCam, "configureCapture", Qt::QueuedConnection, + Q_ARG(QString, dev), Q_ARG(QString, QStringLiteral("h264")), + Q_ARG(int, 640), Q_ARG(int, 480)); + QMetaObject::invokeMethod(testCam, "Capture", Qt::QueuedConnection); + + QMetaObject::invokeMethod(testDecoder, "configurePlayback", Qt::QueuedConnection, + Q_ARG(QString, QStringLiteral("h264")), + Q_ARG(int, 640), Q_ARG(int, 480)); + QMetaObject::invokeMethod(testDecoder, "Play", Qt::QueuedConnection); +} + +void SettingsDialog::stopVideoTest() +{ + // Direct calls: both devices are blocked inside their capture/decode + // loops, so a queued call would never reach them (see the comments in + // AudioVideoCallController::stopAudioPipeline()). + testCam->Close(); + testDecoder->Close(); + + testAssembler.reset(); + videoTestRunning = false; + btVideoTest->setText(tr("Test 📷")); + videoPreview->clear(); +} + +void SettingsDialog::videoTestFragment(QByteArray data, bool is_last_fragment) +{ + if (!videoTestRunning) + return; + + // The encoder emits fragments exactly as they would go onto the wire, so + // feed them through the same assembler the receiving end of a call uses. + // The sequence number is rebuilt here the way send_video_packet() builds + // it, the timestamp being constant since only frame boundaries matter. + quint16 seq = is_last_fragment ? VideoFrameAssembler::LastFragmentFlag : 0; + QByteArray frame = testAssembler.addFragment(seq, 0, data); + + if (frame.isEmpty()) + return; + + // Direct call, for the same reason as in the call controller. + testDecoder->enqueueFrame(frame); +} + +void SettingsDialog::videoTestFrame(QImage frame) +{ + if (!videoTestRunning || frame.isNull()) + return; + + videoPreview->setPixmap(QPixmap::fromImage(frame).scaled(videoPreview->size(), + Qt::KeepAspectRatio, + Qt::SmoothTransformation)); +} + +void SettingsDialog::videoTestError(QString message) +{ + if (!videoTestRunning) + return; + + stopVideoTest(); + videoPreview->setText(message); +} + void SettingsDialog::fillDevicesComboBox(QComboBox *cb, MediaDevice *dev, enum MediaDeviceStreamDirection dir) { cb->clear(); |
