Kstars

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

KDE's Doxygen guidelines are available online.