Kstars

streamwg.cpp
1/*
2 SPDX-FileCopyrightText: 2003 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5
6 2004-03-16: A class to handle video streaming.
7*/
8
9#include "streamwg.h"
10
11#include "kstars.h"
12#include "Options.h"
13#include "kstars_debug.h"
14#include "collimationoverlayoptions.h"
15#include "qobjectdefs.h"
16
17#include <basedevice.h>
18
19#include <KLocalizedString>
20#include <KMessageBox>
21
22#include <QLocale>
23#include <QDebug>
24#include <QPushButton>
25#include <QFileDialog>
26#include <QRgb>
27#include <QSocketNotifier>
28#include <QImage>
29#include <QPainter>
30#include <QDir>
31#include <QLayout>
32#include <QPaintEvent>
33#include <QCloseEvent>
34#include <QImageWriter>
35#include <QImageReader>
36#include <QIcon>
37#include <QTimer>
38
39#include <cstdlib>
40#include <fcntl.h>
41
42RecordOptions::RecordOptions(QWidget *parent) : QDialog(parent)
43{
44#ifdef Q_OS_MACOS
45 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
46#endif
47
48 setupUi(this);
49
51
52 selectDirB->setIcon(
53 QIcon::fromTheme("document-open-folder"));
54 connect(selectDirB, SIGNAL(clicked()), this, SLOT(selectRecordDirectory()));
55}
56
57void RecordOptions::selectRecordDirectory()
58{
59 QString dir =
60 QFileDialog::getExistingDirectory(KStars::Instance(), i18nc("@title:window", "SER Record Directory"),
61 dirPath.toLocalFile());
62
63 if (dir.isEmpty())
64 return;
65
66 recordDirectoryEdit->setText(dir);
67}
68
69StreamWG::StreamWG(ISD::Camera *ccd) : QDialog(KStars::Instance())
70{
71 setupUi(this);
72 m_Camera = ccd;
73 streamWidth = streamHeight = -1;
74 processStream = colorFrame = isRecording = false;
75
76 options = new RecordOptions(this);
77 connect(optionsB, SIGNAL(clicked()), options, SLOT(show()));
78
79 collimationOptionsB->setIcon(QIcon::fromTheme("run-build-prune"));
80 connect(collimationOptionsB, &QPushButton::clicked, this, [this]()
81 {
82 CollimationOverlayOptions::Instance(this)->openEditor();
83 });
84
85 collimationB->setIcon(QIcon::fromTheme("crosshairs"));
86 connect(CollimationOverlayOptions::Instance(this), SIGNAL(updated()), videoFrame, SLOT(modelChanged()));
87 connect(collimationB, &QPushButton::clicked, videoFrame, &VideoWG::toggleOverlay);
88
89 QString filename, directory;
90 ccd->getSERNameDirectory(filename, directory);
91
92 double duration = 0.1;
93 bool hasStreamExposure = m_Camera->getStreamExposure(&duration);
94 if (hasStreamExposure)
95 targetFrameDurationSpin->setValue(duration);
96 else
97 {
98 targetFrameDurationSpin->setEnabled(false);
99 changeFPSB->setEnabled(false);
100 }
101
102 options->recordFilenameEdit->setText(filename);
103 options->recordDirectoryEdit->setText(directory);
104
105 setWindowTitle(i18nc("@title:window", "%1 Live Video", ccd->getDeviceName()));
106
107#if defined(Q_OS_MACOS)
108 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
109#else
110 setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
111#endif
112 recordIcon = QIcon::fromTheme("media-record");
113 stopIcon = QIcon::fromTheme("media-playback-stop");
114
115 optionsB->setIcon(QIcon::fromTheme("run-build"));
116 resetFrameB->setIcon(QIcon::fromTheme("view-restore"));
117
118 connect(resetFrameB, SIGNAL(clicked()), this, SLOT(resetFrame()));
119
120 recordB->setIcon(recordIcon);
121
122 connect(recordB, SIGNAL(clicked()), this, SLOT(toggleRecord()));
123 connect(ccd, SIGNAL(videoRecordToggled(bool)), this, SLOT(updateRecordStatus(bool)));
124
125 connect(videoFrame, &VideoWG::newSelection, this, &StreamWG::setStreamingFrame);
126 connect(videoFrame, &VideoWG::imageChanged, this, &StreamWG::imageChanged);
127
128 resize(Options::streamWindowWidth(), Options::streamWindowHeight());
129
130 eoszoom = m_Camera->getProperty("eoszoom");
131 if (eoszoom == nullptr)
132 {
133 zoomLevelCombo->hide();
134 }
135 else
136 {
137 connect(zoomLevelCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), [&]()
138 {
139 auto tvp = eoszoom->getText();
140 QString zoomLevel = zoomLevelCombo->currentText().remove("x");
141 tvp->at(0)->setText(zoomLevel.toLatin1().constData());
142 handLabel->setEnabled(true);
143 NSSlider->setEnabled(true);
144 WESlider->setEnabled(true);
145 // Set it twice!
146 m_Camera->sendNewProperty(tvp);
147 QTimer::singleShot(1000, this, [ &, tvp]()
148 {
149 m_Camera->sendNewProperty(tvp);
150 });
151
152 });
153 }
154
155 eoszoomposition = m_Camera->getProperty("eoszoomposition");
156 if (eoszoomposition == nullptr)
157 {
158 handLabel->hide();
159 NSSlider->hide();
160 WESlider->hide();
161
162 horizontalSpacer->changeSize(1, 1, QSizePolicy::Expanding);
163 }
164 else
165 {
166 connect(NSSlider, &QSlider::sliderReleased, [&]()
167 {
168 auto tvp = eoszoomposition->getText();
169 QString pos = QString("%1,%2").arg(WESlider->value()).arg(NSSlider->value());
170 tvp->at(0)->setText(pos.toLatin1().constData());
171 m_Camera->sendNewProperty(tvp);
172 });
173
174 connect(WESlider, &QSlider::sliderReleased, [&]()
175 {
176 auto tvp = eoszoomposition->getText();
177 QString pos = QString("%1,%2").arg(WESlider->value()).arg(NSSlider->value());
178 tvp->at(0)->setText(pos.toLatin1().constData());
179 m_Camera->sendNewProperty(tvp);
180 });
181
182 horizontalSpacer->changeSize(1, 1, QSizePolicy::Preferred);
183 }
184
185 connect(m_Camera, &ISD::Camera::newFPS, this, &StreamWG::updateFPS);
186 connect(m_Camera, &ISD::Camera::propertyUpdated, this, [this](INDI::Property prop)
187 {
188 if (prop.isNameMatch("CCD_INFO") || prop.isNameMatch("CCD_CFA"))
189 syncDebayerParameters();
190 });
191 connect(changeFPSB, &QPushButton::clicked, this, [&]()
192 {
193 if (m_Camera)
194 {
195 m_Camera->setStreamExposure(targetFrameDurationSpin->value());
196 m_Camera->setVideoStreamEnabled(false);
197 QTimer::singleShot(1000, this, [&]()
198 {
199 m_Camera->setVideoStreamEnabled(true);
200 });
201 }
202 });
203
204 debayerB->setIcon(QIcon(":/icons/cfa.svg"));
205 connect(debayerB, &QPushButton::clicked, this, [this]()
206 {
207 m_DebayerActive = !m_DebayerActive;
208 });
209 syncDebayerParameters();
210}
211
212void StreamWG::syncDebayerParameters()
213{
214 m_DebayerSupported = queryDebayerParameters();
215 debayerB->setEnabled(m_DebayerSupported);
216 m_DebayerActive = m_DebayerSupported;
217}
218
219bool StreamWG::queryDebayerParameters()
220{
221 if (!m_Camera)
222 return false;
223
224 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
225 if (!targetChip)
226 return false;
227
228 // DSLRs always send motion JPGs when streaming so
229 // bayered images are not streamed.
230 if (targetChip->getISOList().isEmpty() == false)
231 return false;
232
233 uint16_t w, h;
234 QString pattern;
235
236 if (targetChip->getImageInfo(w, h, pixelX, pixelY, m_BBP) == false)
237 return false;
238
239 // Limit only to 8 and 16 bit, nothing in between or less or more.
240 if (m_BBP > 8)
241 m_BBP = 16;
242 else
243 m_BBP = 8;
244
245 if (targetChip->getBayerInfo(offsetX, offsetY, pattern) == false)
246 return false;
247
248 m_DebayerParams.method = DC1394_BAYER_METHOD_NEAREST;
249 m_DebayerParams.filter = DC1394_COLOR_FILTER_RGGB;
250
251 if (pattern == "GBRG")
252 m_DebayerParams.filter = DC1394_COLOR_FILTER_GBRG;
253 else if (pattern == "GRBG")
254 m_DebayerParams.filter = DC1394_COLOR_FILTER_GRBG;
255 else if (pattern == "BGGR")
256 m_DebayerParams.filter = DC1394_COLOR_FILTER_BGGR;
257
258 return true;
259}
260
261QSize StreamWG::sizeHint() const
262{
263 QSize size(Options::streamWindowWidth(), Options::streamWindowHeight());
264 return size;
265}
266
267void StreamWG::showEvent(QShowEvent *ev)
268{
269 if (m_Camera)
270 {
271 // Always reset to 1x for DSLRs since they reset whenever they are triggered again.
272 if (eoszoom)
273 zoomLevelCombo->setCurrentIndex(0);
274 }
275
276 ev->accept();
277}
278
279void StreamWG::closeEvent(QCloseEvent * ev)
280{
281 processStream = false;
282
283 Options::setStreamWindowWidth(width());
284 Options::setStreamWindowHeight(height());
285
286 ev->accept();
287
288 emit hidden();
289}
290
291void StreamWG::setColorFrame(bool color)
292{
293 colorFrame = color;
294}
295
296void StreamWG::enableStream(bool enable)
297{
298 if (enable)
299 {
300 processStream = true;
301 show();
302 }
303 else
304 {
305 processStream = false;
306 //instFPS->setText("--");
307 avgFPS->setText("--");
308 hide();
309 }
310}
311
312void StreamWG::setSize(int wd, int ht)
313{
314 if (wd != streamWidth || ht != streamHeight)
315 {
316 streamWidth = wd;
317 streamHeight = ht;
318
319 NSSlider->setMaximum(ht);
320 NSSlider->setSingleStep(ht / 30);
321 WESlider->setMaximum(wd);
322 WESlider->setSingleStep(wd / 30);
323
324 videoFrame->setSize(wd, ht);
325 }
326}
327
328/*void StreamWG::resizeEvent(QResizeEvent *ev)
329{
330 streamFrame->resize(ev->size().width() - layout()->margin() * 2, ev->size().height() - playB->height() - layout()->margin() * 4 - layout()->spacing());
331
332}*/
333
334void StreamWG::updateRecordStatus(bool enabled)
335{
336 if ((enabled && isRecording) || (!enabled && !isRecording))
337 return;
338
339 isRecording = enabled;
340
341 if (isRecording)
342 {
343 recordB->setIcon(stopIcon);
344 recordB->setToolTip(i18n("Stop recording"));
345 }
346 else
347 {
348 recordB->setIcon(recordIcon);
349 recordB->setToolTip(i18n("Start recording"));
350 }
351}
352
353void StreamWG::toggleRecord()
354{
355 if (isRecording)
356 {
357 recordB->setIcon(recordIcon);
358 isRecording = false;
359 recordB->setToolTip(i18n("Start recording"));
360
361 m_Camera->stopRecording();
362 }
363 else
364 {
365 QString directory, filename;
366 m_Camera->getSERNameDirectory(filename, directory);
367 if (filename != options->recordFilenameEdit->text() ||
368 directory != options->recordDirectoryEdit->text())
369 {
370 m_Camera->setSERNameDirectory(options->recordFilenameEdit->text(), options->recordDirectoryEdit->text());
371 // Save config in INDI so the filename and directory templates are reloaded next time
372 m_Camera->setConfig(SAVE_CONFIG);
373 }
374
375 if (options->recordUntilStoppedR->isChecked())
376 {
377 isRecording = m_Camera->startRecording();
378 }
379 else if (options->recordDurationR->isChecked())
380 {
381 isRecording = m_Camera->startDurationRecording(options->durationSpin->value());
382 }
383 else
384 {
385 isRecording = m_Camera->startFramesRecording(options->framesSpin->value());
386 }
387
388 if (isRecording)
389 {
390 recordB->setIcon(stopIcon);
391 recordB->setToolTip(i18n("Stop recording"));
392 }
393 }
394}
395
396void StreamWG::newFrame(INDI::Property prop)
397{
398 auto bp = prop.getBLOB()->at(0);
399
400 bool rc = (m_DebayerActive
401 && !strcmp(bp->getFormat(), ".stream")) ? videoFrame->newBayerFrame(bp, m_DebayerParams) : videoFrame->newFrame(bp);
402
403 if (rc == false)
404 qCWarning(KSTARS) << "Failed to load video frame.";
405}
406
407void StreamWG::resetFrame()
408{
409 m_Camera->resetStreamingFrame();
410}
411
412void StreamWG::setStreamingFrame(QRect newFrame)
413{
414 if (newFrame.isNull())
415 {
416 resetFrame();
417 return;
418 }
419
420 if (newFrame.width() < 5 || newFrame.height() < 5)
421 return;
422
423 int w = newFrame.width();
424 // Must be divisable by 4
425 while (w % 4)
426 {
427 w++;
428 }
429
430 m_Camera->setStreamingFrame(newFrame.x(), newFrame.y(), w, newFrame.height());
431}
432
433void StreamWG::updateFPS(double instantFPS, double averageFPS)
434{
435 Q_UNUSED(instantFPS)
436 //instFPS->setText(QString::number(instantFPS, 'f', 1));
437 avgFPS->setText(QString::number(averageFPS, 'f', 1));
438}
439
440StreamWG::~StreamWG()
441{
442 CollimationOverlayOptions::Instance(this)->release();
443}
CameraChip class controls a particular chip in camera.
Camera class controls an INDI Camera device.
Definition indicamera.h:45
This is the main window for KStars.
Definition kstars.h:89
static KStars * Instance()
Definition kstars.h:121
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT QString dir(const QString &fileClass)
void clicked(bool checked)
void sliderReleased()
const char * constData() const const
void activated(int index)
QString homePath()
void accept()
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, Options options)
QIcon fromTheme(const QString &name)
bool isEmpty() const const
int height() const const
bool isNull() const const
int width() const const
int x() const const
int y() const const
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QByteArray toLatin1() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUrl fromLocalFile(const QString &localFile)
QString toLocalFile() const const
void hide()
void show()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Wed Nov 6 2024 12:13:15 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.