Instrument Neutral Distributed Interface INDI  2.0.2
streammanager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2020 by Pawel Soja <kernel32.pl@gmail.com>
3  Copyright (C) 2015 by Jasem Mutlaq <mutlaqja@ikarustech.com>
4  Copyright (C) 2014 by geehalel <geehalel@gmail.com>
5 
6  Stream Recorder
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Lesser General Public
10  License as published by the Free Software Foundation; either
11  version 2.1 of the License, or (at your option) any later version.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  Lesser General Public License for more details.
17 
18  You should have received a copy of the GNU Lesser General Public
19  License along with this library; if not, write to the Free Software
20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 
22 */
23 
24 #include <config.h>
25 #include "streammanager.h"
26 #include "streammanager_p.h"
27 #include "indiccd.h"
28 #include "indisensorinterface.h"
29 #include "indilogger.h"
30 #include "indiutility.h"
31 #include "indisinglethreadpool.h"
32 #include "indielapsedtimer.h"
33 
34 #include <cerrno>
35 #include <sys/stat.h>
36 
37 #include <algorithm>
38 
39 static const char * STREAM_TAB = "Streaming";
40 
41 namespace INDI
42 {
43 
45  : currentDevice(defaultDevice)
46 {
48 #ifdef __arm__
50 #else
52 #endif
53 
55 
56  LOGF_DEBUG("Using default recorder (%s)", recorder->getName());
57 
59 
61 
62  LOGF_DEBUG("Using default encoder (%s)", encoder->getName());
63 
65 }
66 
68 {
69  if (framesThread.joinable())
70  {
71  framesThreadTerminate = true;
72  framesIncoming.abort();
73  framesThread.join();
74  }
75 }
76 
78  : d_ptr(new StreamManagerPrivate(mainDevice))
79 { }
80 
82 { }
83 
85 {
86  return currentDevice->getDeviceName();
87 }
88 
89 const char * StreamManager::getDeviceName() const
90 {
91  D_PTR(const StreamManager);
92  return d->getDeviceName();
93 }
94 
96 {
97  /* Video Stream */
98  StreamSP[0].fill("STREAM_ON", "Stream On", ISS_OFF);
99  StreamSP[1].fill("STREAM_OFF", "Stream Off", ISS_ON);
101  StreamSP.fill(getDeviceName(), "SENSOR_DATA_STREAM", "Video Stream",
102  STREAM_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
103  else
104  StreamSP.fill(getDeviceName(), "CCD_VIDEO_STREAM", "Video Stream",
105  STREAM_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
106 
107  StreamTimeNP[0].fill("STREAM_DELAY_TIME", "Delay (s)", "%.3f", 0, 60, 0.001, 0);
108  StreamTimeNP.fill(getDeviceName(), "STREAM_DELAY", "Video Stream Delay", STREAM_TAB, IP_RO, 0, IPS_IDLE);
109 
110  StreamExposureNP[STREAM_EXPOSURE].fill("STREAMING_EXPOSURE_VALUE", "Duration (s)", "%.6f", 0.000001, 60, 0.1, 0.1);
111  StreamExposureNP[STREAM_DIVISOR ].fill("STREAMING_DIVISOR_VALUE", "Divisor", "%.f", 1, 15, 1.0, 1.0);
112  StreamExposureNP.fill(getDeviceName(), "STREAMING_EXPOSURE", "Expose", STREAM_TAB, IP_RW, 60, IPS_IDLE);
113 
114  /* Measured FPS */
115  FpsNP[FPS_INSTANT].fill("EST_FPS", "Instant.", "%.2f", 0.0, 999.0, 0.0, 30);
116  FpsNP[FPS_AVERAGE].fill("AVG_FPS", "Average (1 sec.)", "%.2f", 0.0, 999.0, 0.0, 30);
117  FpsNP.fill(getDeviceName(), "FPS", "FPS", STREAM_TAB, IP_RO, 60, IPS_IDLE);
118 
119  /* Record Frames */
120  /* File */
121  std::string defaultDirectory = std::string(getenv("HOME")) + std::string("/indi__D_");
122  RecordFileTP[0].fill("RECORD_FILE_DIR", "Dir.", defaultDirectory.data());
123  RecordFileTP[1].fill("RECORD_FILE_NAME", "Name", "indi_record__T_");
124  RecordFileTP.fill(getDeviceName(), "RECORD_FILE", "Record File",
125  STREAM_TAB, IP_RW, 0, IPS_IDLE);
126 
127  /* Record Options */
128  RecordOptionsNP[0].fill("RECORD_DURATION", "Duration (sec)", "%.3f", 0.001, 999999.0, 0.0, 1.0);
129  RecordOptionsNP[1].fill("RECORD_FRAME_TOTAL", "Frames", "%.f", 1.0, 999999999.0, 1.0, 30.0);
130  RecordOptionsNP.fill(getDeviceName(), "RECORD_OPTIONS",
131  "Record Options", STREAM_TAB, IP_RW, 60, IPS_IDLE);
132 
133  /* Record Switch */
134  RecordStreamSP[RECORD_ON ].fill("RECORD_ON", "Record On", ISS_OFF);
135  RecordStreamSP[RECORD_TIME ].fill("RECORD_DURATION_ON", "Record (Duration)", ISS_OFF);
136  RecordStreamSP[RECORD_FRAME].fill("RECORD_FRAME_ON", "Record (Frames)", ISS_OFF);
137  RecordStreamSP[RECORD_OFF ].fill("RECORD_OFF", "Record Off", ISS_ON);
138  RecordStreamSP.fill(getDeviceName(), "RECORD_STREAM", "Video Record", STREAM_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
139 
141  {
142  // CCD Streaming Frame
143  StreamFrameNP[0].fill("X", "Left", "%.f", 0, 0, 0, 0);
144  StreamFrameNP[1].fill("Y", "Top", "%.f", 0, 0, 0, 0);
145  StreamFrameNP[2].fill("WIDTH", "Width", "%.f", 0, 0, 0, 0);
146  StreamFrameNP[3].fill("HEIGHT", "Height", "%.f", 0, 0, 0, 0);
147  StreamFrameNP.fill(getDeviceName(), "CCD_STREAM_FRAME", "Frame", STREAM_TAB, IP_RW,
148  60, IPS_IDLE);
149  }
150 
151  // Encoder Selection
152  EncoderSP[ENCODER_RAW ].fill("RAW", "RAW", ISS_ON);
153  EncoderSP[ENCODER_MJPEG].fill("MJPEG", "MJPEG", ISS_OFF);
155  EncoderSP.fill(getDeviceName(), "SENSOR_STREAM_ENCODER", "Encoder", STREAM_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
156  else
157  EncoderSP.fill(getDeviceName(), "CCD_STREAM_ENCODER", "Encoder", STREAM_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
158 
159  // Recorder Selector
160  RecorderSP[RECORDER_RAW].fill("SER", "SER", ISS_ON);
161  RecorderSP[RECORDER_OGV].fill("OGV", "OGV", ISS_OFF);
163  RecorderSP.fill(getDeviceName(), "SENSOR_STREAM_RECORDER", "Recorder", STREAM_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
164  else
165  RecorderSP.fill(getDeviceName(), "CCD_STREAM_RECORDER", "Recorder", STREAM_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
166 
167  // If we do not have theora installed, let's just define SER default recorder
168 #ifndef HAVE_THEORA
169  RecorderSP.resize(1);
170 #endif
171 
172  // Limits
173  LimitsNP[LIMITS_BUFFER_MAX ].fill("LIMITS_BUFFER_MAX", "Maximum Buffer Size (MB)", "%.0f", 1, 1024 * 64, 1, 512);
174  LimitsNP[LIMITS_PREVIEW_FPS].fill("LIMITS_PREVIEW_FPS", "Maximum Preview FPS", "%.0f", 1, 120, 1, 10);
175  LimitsNP.fill(getDeviceName(), "LIMITS", "Limits", STREAM_TAB, IP_RW, 0, IPS_IDLE);
176  return true;
177 }
178 
180 {
181  D_PTR(StreamManager);
182  return d->initProperties();
183 }
184 
186 {
187  if (dev != nullptr && strcmp(getDeviceName(), dev))
188  return;
189 
190  if (currentDevice->isConnected())
191  {
203  }
204 }
205 
206 void StreamManager::ISGetProperties(const char * dev)
207 {
208  D_PTR(StreamManager);
209  d->ISGetProperties(dev);
210 }
211 
213 {
214  if (currentDevice->isConnected())
215  {
217  {
218  imageBP = currentDevice->getBLOB("CCD1");
219  }
221  {
222  imageBP = currentDevice->getBLOB("SENSOR");
223  }
224 
237  }
238  else
239  {
252  }
253 
254  return true;
255 }
256 
258 {
259  D_PTR(StreamManager);
260  return d->updateProperties();
261 }
262 
263 /*
264  * The camera driver is expected to send the FULL FRAME of the Camera after BINNING without any subframing at all
265  * Subframing for streaming/recording is done in the stream manager.
266  * Therefore nbytes is expected to be SubW/BinX * SubH/BinY * Bytes_Per_Pixels * Number_Color_Components
267  * Binned frame must be sent from the camera driver for this to work consistentaly for all drivers.*/
268 void StreamManagerPrivate::newFrame(const uint8_t * buffer, uint32_t nbytes, uint64_t timestamp)
269 {
270  // close the data stream on the same thread as the data stream
271  // manually triggered to stop recording.
273  {
274  stopRecording();
275  return;
276  }
277 
278  // Discard every N frame.
279  // do not count it to fps statistics
280  // N is StreamExposureNP[STREAM_DIVISOR].getValue()
282  if (
283  (StreamExposureNP[STREAM_DIVISOR].getValue() > 1) &&
284  (frameCountDivider % static_cast<int>(StreamExposureNP[STREAM_DIVISOR].getValue())) == 0
285  )
286  {
287  return;
288  }
289 
290  if (FPSAverage.newFrame())
291  {
292  FpsNP[1].setValue(FPSAverage.framesPerSecond());
293  }
294 
295  if (FPSFast.newFrame())
296  {
297  FpsNP[0].setValue(FPSFast.framesPerSecond());
298  if (fastFPSUpdate.try_lock()) // don't block stream thread / record thread
299  std::thread([&]()
300  {
301  FpsNP.apply();
302  fastFPSUpdate.unlock();
303  }).detach();
304  }
305 
307  {
308  size_t allocatedSize = nbytes * framesIncoming.size() / 1024 / 1024; // allocated size in MB
309  if (allocatedSize > LimitsNP[LIMITS_BUFFER_MAX].getValue())
310  {
311  LOG_WARN("Frame buffer is full, skipping frame...");
312  return;
313  }
314 
315  std::vector<uint8_t> copyBuffer(buffer, buffer + nbytes); // copy the frame
316 
317  framesIncoming.push(TimeFrame{FPSFast.deltaTime(), timestamp, std::move(copyBuffer)}); // push it into the queue
318  }
319 
321  {
322  FPSRecorder.newFrame(); // count frames and total time
323 
324  // captured all frames, stream should be close
325  if (
326  (RecordStreamSP[RECORD_FRAME].getState() == ISS_ON && FPSRecorder.totalFrames() >= (RecordOptionsNP[1].getValue())) ||
327  (RecordStreamSP[RECORD_TIME ].getState() == ISS_ON && FPSRecorder.totalTime() >= (RecordOptionsNP[0].getValue() * 1000.0))
328  )
329  {
330  LOG_INFO("Waiting for all buffered frames to be recorded");
331  framesIncoming.waitForEmpty();
332  // duplicated message
333 #if 0
334  LOGF_INFO(
335  "Ending record after %g millisecs and %d frames",
338  );
339 #endif
344 
345  stopRecording();
346  }
347  }
348 }
349 
350 void StreamManager::newFrame(const uint8_t * buffer, uint32_t nbytes, uint64_t timestamp)
351 {
352  D_PTR(StreamManager);
353  d->newFrame(buffer, nbytes, timestamp);
354 }
355 
356 
358 {
359  FrameInfo srcFrameInfo;
360 
361  uint8_t components = (PixelFormat == INDI_RGB) ? 3 : 1;
362  uint8_t bytesPerComponent = (PixelDepth + 7) / 8;
363 
364  dstFrameInfo.bytesPerColor = components * bytesPerComponent;
365 
367  {
368  srcFrameInfo = FrameInfo(
369  dynamic_cast<const INDI::CCD*>(currentDevice)->PrimaryCCD,
370  components * bytesPerComponent
371  );
372  }
374  {
375  srcFrameInfo = FrameInfo(
376  *dynamic_cast<const INDI::SensorInterface*>(currentDevice),
377  components * bytesPerComponent
378  );
379  }
380 
381  // If stream frame was not yet initialized, let's do that now
382  if (dstFrameInfo.pixels() == 0)
383  {
384  //if (dynamic_cast<INDI::CCD*>(currentDevice)->PrimaryCCD.getNAxis() == 2)
385  // binFactor = dynamic_cast<INDI::CCD*>(currentDevice)->PrimaryCCD.getBinX();
386  dstFrameInfo = srcFrameInfo;
390  }
391 
392  return srcFrameInfo;
393 }
394 
396  const uint8_t *srcBuffer,
397  const FrameInfo &srcFrameInfo,
398  uint8_t *dstBuffer,
399  const FrameInfo &dstFrameInfo
400 )
401 {
402  size_t srcOffset = srcFrameInfo.bytesPerColor * (dstFrameInfo.y * srcFrameInfo.w + dstFrameInfo.x);
403  uint32_t srcStride = srcFrameInfo.lineSize();
404  uint32_t dstStride = dstFrameInfo.lineSize();
405 
406  srcBuffer += srcOffset;
407 
408  // Copy line-by-line
409  for (size_t i = 0; i < dstFrameInfo.h; ++i)
410  {
411  memcpy(dstBuffer, srcBuffer, dstStride);
412  dstBuffer += dstStride;
413  srcBuffer += srcStride;
414  }
415 }
416 
418 {
419  TimeFrame sourceTimeFrame;
420  sourceTimeFrame.time = 0;
421 
422  std::vector<uint8_t> subframeBuffer; // Subframe buffer for recording/streaming
423  std::vector<uint8_t> downscaleBuffer; // Downscale buffer for streaming
424 
425  INDI::SingleThreadPool previewThreadPool;
426  INDI::ElapsedTimer previewElapsed;
427 
428  while(!framesThreadTerminate)
429  {
430  if (framesIncoming.pop(sourceTimeFrame) == false)
431  continue;
432 
433  FrameInfo srcFrameInfo = updateSourceFrameInfo();
434 
435  std::vector<uint8_t> *sourceBuffer = &sourceTimeFrame.frame;
436 
437  if (PixelFormat != INDI_JPG && sourceBuffer->size() != srcFrameInfo.totalSize())
438  {
439  LOG_ERROR("Invalid source buffer size, skipping frame...");
440  continue;
441  }
442 
443  // Check if we need to subframe
444  if (
445  PixelFormat != INDI_JPG &&
446  dstFrameInfo.pixels() != 0 &&
447  dstFrameInfo != srcFrameInfo
448  )
449  {
450  subframeBuffer.resize(dstFrameInfo.totalSize());
451  subframe(sourceBuffer->data(), srcFrameInfo, subframeBuffer.data(), dstFrameInfo);
452 
453  sourceBuffer = &subframeBuffer;
454  }
455 
456  // For recording, save immediately.
457  {
458  std::lock_guard<std::mutex> lock(recordMutex);
459  if (
461  recordStream(sourceBuffer->data(), sourceBuffer->size(), sourceTimeFrame.time, sourceTimeFrame.timestamp) == false
462  )
463  {
464  LOG_ERROR("Recording failed.");
466  }
467  }
468 
469  // For streaming, downscale to 8bit if higher than 8bit to reduce bandwidth
470  // You can reduce the number of frames by setting a frame limit.
472  {
473  // Downscale to 8bit always for streaming to reduce bandwidth
474  if (PixelFormat != INDI_JPG && PixelDepth > 8)
475  {
476  // Allocale new buffer if size changes
477  downscaleBuffer.resize(dstFrameInfo.pixels());
478 
479  // Apply gamma
481  reinterpret_cast<const uint16_t*>(sourceBuffer->data()),
482  downscaleBuffer.size(),
483  downscaleBuffer.data()
484  );
485 
486  sourceBuffer = &downscaleBuffer;
487  }
488 
489  //uploadStream(sourceBuffer->data(), sourceBuffer->size());
490  previewThreadPool.start(std::bind([this, &previewElapsed](const std::atomic_bool & isAboutToQuit,
491  std::vector<uint8_t> frame)
492  {
493  INDI_UNUSED(isAboutToQuit);
494  previewElapsed.start();
495  uploadStream(frame.data(), frame.size());
496  StreamTimeNP[0].setValue(previewElapsed.nsecsElapsed() / 1000000000.0);
498 
499  }, std::placeholders::_1, std::move(*sourceBuffer)));
500  }
501  }
502 }
503 
504 void StreamManagerPrivate::setSize(uint16_t width, uint16_t height)
505 {
506  if (width != StreamFrameNP[CCDChip::FRAME_W].getValue() || height != StreamFrameNP[CCDChip::FRAME_H].getValue())
507  {
508  if (PixelFormat == INDI_JPG)
509  LOG_WARN("Cannot subframe JPEG streams.");
510 
511  StreamFrameNP[CCDChip::FRAME_X].setValue(0);
512  StreamFrameNP[CCDChip::FRAME_X].setMax(width - 1);
513  StreamFrameNP[CCDChip::FRAME_Y].setValue(0);
514  StreamFrameNP[CCDChip::FRAME_Y].setMax(height - 1);
515  StreamFrameNP[CCDChip::FRAME_W].setValue(width);
516  StreamFrameNP[CCDChip::FRAME_W].setMin(10);
517  StreamFrameNP[CCDChip::FRAME_W].setMax(width);
518  StreamFrameNP[CCDChip::FRAME_H].setValue(height);
519  StreamFrameNP[CCDChip::FRAME_H].setMin(10);
520  StreamFrameNP[CCDChip::FRAME_H].setMax(height);
521 
524  }
525 
530 
531  // Width & Height are BINNED dimensions.
532  // Since they're the final size to make it to encoders and recorders.
533  rawWidth = width;
534  rawHeight = height;
535 
536  for (EncoderInterface * oneEncoder : encoderManager.getEncoderList())
537  oneEncoder->setSize(rawWidth, rawHeight);
538  for (RecorderInterface * oneRecorder : recorderManager.getRecorderList())
539  oneRecorder->setSize(rawWidth, rawHeight);
540 }
541 
543 {
544  D_PTR(StreamManager);
545  std::lock_guard<std::mutex> lock(d->recordMutex);
546  return d->recorder->close();
547 }
548 
549 bool StreamManagerPrivate::setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth)
550 {
551  if (pixelFormat == PixelFormat && pixelDepth == PixelDepth)
552  return true;
553 
554  bool recorderOK = recorder->setPixelFormat(pixelFormat, pixelDepth);
555  if (recorderOK == false)
556  {
557  LOGF_ERROR("Pixel format %d is not supported by %s recorder.", pixelFormat, recorder->getName());
558  }
559  else
560  {
561  LOGF_DEBUG("Pixel format %d is supported by %s recorder.", pixelFormat, recorder->getName());
562  }
563  bool encoderOK = encoder->setPixelFormat(pixelFormat, pixelDepth);
564  if (encoderOK == false)
565  {
566  LOGF_ERROR("Pixel format %d is not supported by %s encoder.", pixelFormat, encoder->getName());
567  }
568  else
569  {
570  LOGF_DEBUG("Pixel format %d is supported by %s encoder.", pixelFormat, encoder->getName());
571  }
572 
573  PixelFormat = pixelFormat;
574  PixelDepth = pixelDepth;
575  return true;
576 }
577 
578 bool StreamManager::setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth)
579 {
580  D_PTR(StreamManager);
581  return d->setPixelFormat(pixelFormat, pixelDepth);
582 }
583 
584 
585 void StreamManager::setSize(uint16_t width, uint16_t height)
586 {
587  D_PTR(StreamManager);
588  d->setSize(width, height);
589 }
590 
591 bool StreamManagerPrivate::recordStream(const uint8_t * buffer, uint32_t nbytes, double deltams, uint64_t timestamp)
592 {
593  INDI_UNUSED(deltams);
594  if (!isRecording)
595  return false;
596 
597  return recorder->writeFrame(buffer, nbytes, timestamp);
598 }
599 
600 std::string StreamManagerPrivate::expand(const std::string &fname, const std::map<std::string, std::string> &patterns)
601 {
602  std::string result = fname;
603 
604  std::time_t t = std::time(nullptr);
605  std::tm tm = *std::gmtime(&t);
606 
607  auto extendedPatterns = patterns;
608  extendedPatterns["_D_"] = format_time(tm, "%Y-%m-%d");
609  extendedPatterns["_H_"] = format_time(tm, "%H-%M-%S");
610  extendedPatterns["_T_"] = format_time(tm, "%Y-%m-%d" "@" "%H-%M-%S");
611 
612  for(const auto &pattern : extendedPatterns)
613  {
614  replace_all(result, pattern.first, pattern.second);
615  }
616 
617  // Replace all : to - to be valid filename on Windows
618  std::replace(result.begin(), result.end(), ':', '-'); // it's really needed now?
619 
620  return result;
621 }
622 
624 {
625  char errmsg[MAXRBUF];
626  std::string filename, expfilename, expfiledir;
627  std::string filtername;
628  std::map<std::string, std::string> patterns;
629  if (isRecording)
630  return true;
631 
633  {
634  /* get filter name for pattern substitution */
635  if (dynamic_cast<INDI::CCD*>(currentDevice)->CurrentFilterSlot != -1
636  && dynamic_cast<INDI::CCD*>(currentDevice)->CurrentFilterSlot <= static_cast<int>(dynamic_cast<INDI::CCD*>
637  (currentDevice)->FilterNames.size()))
638  {
639  filtername = dynamic_cast<INDI::CCD*>(currentDevice)->FilterNames.at(dynamic_cast<INDI::CCD*>
641  patterns["_F_"] = filtername;
642  LOGF_DEBUG("Adding filter pattern %s", filtername.c_str());
643  }
644  }
645 
646  recorder->setFPS(FpsNP[FPS_AVERAGE].getValue());
647 
648  /* pattern substitution */
649  recordfiledir.assign(RecordFileTP[0].getText());
650  expfiledir = expand(recordfiledir, patterns);
651  if (expfiledir.at(expfiledir.size() - 1) != '/')
652  expfiledir += '/';
653  recordfilename.assign(RecordFileTP[1].getText());
654  expfilename = expand(recordfilename, patterns);
655  if (expfilename.size() < 4 || expfilename.substr(expfilename.size() - 4, 4) != recorder->getExtension())
656  expfilename += recorder->getExtension();
657 
658  filename = expfiledir + expfilename;
659  //LOGF_INFO("Expanded file is %s", filename.c_str());
660  //filename=recordfiledir+recordfilename;
661  LOGF_INFO("Record file is %s", filename.c_str());
662  /* Create/open file/dir */
663  if (mkpath(expfiledir, 0755))
664  {
665  LOGF_WARN("Can not create record directory %s: %s", expfiledir.c_str(),
666  strerror(errno));
667  return false;
668  }
669  if (!recorder->open(filename.c_str(), errmsg))
670  {
673  LOGF_WARN("Can not open record file: %s", errmsg);
674  return false;
675  }
676 
677 #if 0
678  /* start capture */
679  // TODO direct recording should this be part of StreamManager?
680  if (direct_record)
681  {
682  LOG_INFO("Using direct recording (no software cropping).");
683  //v4l_base->doDecode(false);
684  //v4l_base->doRecord(true);
685  }
686  else
687  {
688  //if (ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON)
689  if (dynamic_cast<INDI::CCD*>(currentDevice)->PrimaryCCD.getNAxis() == 2)
690  recorder->setDefaultMono();
691  else
692  recorder->setDefaultColor();
693  }
694 #endif
695  FPSRecorder.reset();
696  frameCountDivider = 0;
697 
698  if (isStreaming == false)
699  {
700  FPSAverage.reset();
701  FPSFast.reset();
702  }
703 
705  {
706  if (isStreaming == false && dynamic_cast<INDI::CCD*>(currentDevice)->StartStreaming() == false)
707  {
708  LOG_ERROR("Failed to start recording.");
713  }
714  }
716  {
717  if (isStreaming == false && dynamic_cast<INDI::SensorInterface*>(currentDevice)->StartStreaming() == false)
718  {
719  LOG_ERROR("Failed to start recording.");
724  }
725  }
726  isRecording = true;
727  return true;
728 }
729 
731 {
732  if (!isRecording && force == false)
733  return true;
734 
736  {
737  if (!isStreaming)
738  dynamic_cast<INDI::CCD*>(currentDevice)->StopStreaming();
739  }
741  {
742  if (!isStreaming)
743  dynamic_cast<INDI::SensorInterface*>(currentDevice)->StopStreaming();
744 
745  }
746 
747  isRecording = false;
748  isRecordingAboutToClose = false;
749 
750  {
751  std::lock_guard<std::mutex> lock(recordMutex);
752  recorder->close();
753  }
754 
755  if (force)
756  return false;
757 
758  LOGF_INFO(
759  "Record Duration: %g millisec / %d frames",
762  );
763 
764  return true;
765 }
766 
767 bool StreamManagerPrivate::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
768 {
769  /* ignore if not ours */
770  if (dev != nullptr && strcmp(getDeviceName(), dev))
771  return false;
772 
773  /* Video Stream */
774  if (StreamSP.isNameMatch(name))
775  {
776  for (int i = 0; i < n; i++)
777  {
778  if (!strcmp(names[i], "STREAM_ON") && states[i] == ISS_ON)
779  {
780  setStream(true);
781  break;
782  }
783  else if (!strcmp(names[i], "STREAM_OFF") && states[i] == ISS_ON)
784  {
785  setStream(false);
786  break;
787  }
788  }
789  return true;
790  }
791 
792  // Record Stream
793  if (RecordStreamSP.isNameMatch(name))
794  {
795  int prevSwitch = RecordStreamSP.findOnSwitchIndex();
796  RecordStreamSP.update(states, names, n);
797 
798  if (isRecording && RecordStreamSP[RECORD_OFF].getState() != ISS_ON)
799  {
801  RecordStreamSP[prevSwitch].setState(ISS_ON);
803  LOG_WARN("Recording device is busy.");
804  return true;
805  }
806 
807  if (
808  RecordStreamSP[RECORD_ON ].getState() == ISS_ON ||
809  RecordStreamSP[RECORD_TIME ].getState() == ISS_ON ||
810  RecordStreamSP[RECORD_FRAME].getState() == ISS_ON
811  )
812  {
813  if (!isRecording)
814  {
816  if (RecordStreamSP[RECORD_TIME].getState() == ISS_ON)
817  LOGF_INFO("Starting video record (Duration): %g secs.", RecordOptionsNP[0].getValue());
818  else if (RecordStreamSP[RECORD_FRAME].getState() == ISS_ON)
819  LOGF_INFO("Starting video record (Frame count): %d.", static_cast<int>(RecordOptionsNP[1].getValue()));
820  else
821  LOG_INFO("Starting video record.");
822 
823  if (!startRecording())
824  {
828  }
829  }
830  }
831  else
832  {
834  Format.clear();
835  FpsNP[FPS_INSTANT].setValue(0);
836  FpsNP[FPS_AVERAGE].setValue(0);
837  if (isRecording)
838  {
839  LOG_INFO("Recording stream has been disabled. Closing the stream...");
841  }
842  }
843 
845  return true;
846  }
847 
848  // Encoder Selection
849  if (EncoderSP.isNameMatch(name))
850  {
851  EncoderSP.update(states, names, n);
853 
854  const char * selectedEncoder = EncoderSP.findOnSwitch()->getName();
855 
856  for (EncoderInterface * oneEncoder : encoderManager.getEncoderList())
857  {
858  if (!strcmp(selectedEncoder, oneEncoder->getName()))
859  {
860  encoderManager.setEncoder(oneEncoder);
861 
862  oneEncoder->setPixelFormat(PixelFormat, PixelDepth);
863 
864  encoder = oneEncoder;
865 
867  }
868  }
869  EncoderSP.apply();
870  return true;
871  }
872 
873  // Recorder Selection
874  if (RecorderSP.isNameMatch(name))
875  {
876  RecorderSP.update(states, names, n);
878 
879  const char * selectedRecorder = RecorderSP.findOnSwitch()->getName();
880 
881  for (RecorderInterface * oneRecorder : recorderManager.getRecorderList())
882  {
883  if (!strcmp(selectedRecorder, oneRecorder->getName()))
884  {
885  recorderManager.setRecorder(oneRecorder);
886 
887  oneRecorder->setPixelFormat(PixelFormat, PixelDepth);
888 
889  recorder = oneRecorder;
890 
892  }
893  }
894  RecorderSP.apply();
895  return true;
896  }
897 
898  // No properties were processed
899  return false;
900 }
901 
902 bool StreamManager::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
903 {
904  D_PTR(StreamManager);
905  return d->ISNewSwitch(dev, name, states, names, n);
906 }
907 
908 bool StreamManagerPrivate::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
909 {
910  /* ignore if not ours */
911  if (dev != nullptr && strcmp(getDeviceName(), dev))
912  return false;
913 
914  if (RecordFileTP.isNameMatch(name))
915  {
916  auto tp = RecordFileTP.findWidgetByName("RECORD_FILE_NAME");
917  if (strchr(tp->getText(), '/'))
918  {
919  LOG_WARN("Dir. separator (/) not allowed in filename.");
920  return true;
921  }
922 
923  RecordFileTP.update(texts, names, n);
925  return true;
926  }
927 
928  // No Properties were processed.
929  return false;
930 }
931 
932 bool StreamManager::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
933 {
934  D_PTR(StreamManager);
935  return d->ISNewText(dev, name, texts, names, n);
936 }
937 
938 bool StreamManagerPrivate::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
939 {
940  /* ignore if not ours */
941  if (dev != nullptr && strcmp(getDeviceName(), dev))
942  return false;
943 
944  if (StreamExposureNP.isNameMatch(name))
945  {
946  StreamExposureNP.update(values, names, n);
949  return true;
950  }
951 
952  /* Limits */
953  if (LimitsNP.isNameMatch(name))
954  {
955  LimitsNP.update(values, names, n);
956 
957  FPSPreview.setTimeWindow(1000.0 / LimitsNP[LIMITS_PREVIEW_FPS].getValue());
958  FPSPreview.reset();
959 
961  LimitsNP.apply();
962  return true;
963  }
964 
965  /* Record Options */
966  if (RecordOptionsNP.isNameMatch(name))
967  {
968  if (isRecording)
969  {
970  LOG_WARN("Recording device is busy");
971  return true;
972  }
973 
974  RecordOptionsNP.update(values, names, n);
977  return true;
978  }
979 
980  /* Stream Frame */
981  if (StreamFrameNP.isNameMatch(name))
982  {
983  if (isRecording)
984  {
985  LOG_WARN("Recording device is busy");
986  return true;
987  }
988 
989  FrameInfo srcFrameInfo;
990 
992  {
993  srcFrameInfo = FrameInfo(dynamic_cast<const INDI::CCD*>(currentDevice)->PrimaryCCD);
994  }
996  {
997  srcFrameInfo = FrameInfo(*dynamic_cast<const INDI::SensorInterface*>(currentDevice));
998  }
999 
1000  StreamFrameNP.update(values, names, n);
1002 
1003  double subW = srcFrameInfo.w - StreamFrameNP[CCDChip::FRAME_X].getValue();
1004  double subH = srcFrameInfo.h - StreamFrameNP[CCDChip::FRAME_Y].getValue();
1005 
1008 
1010 
1011  StreamFrameNP.apply();
1012  return true;
1013  }
1014 
1015  // No properties were processed
1016  return false;
1017 }
1018 
1019 bool StreamManager::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
1020 {
1021  D_PTR(StreamManager);
1022  return d->ISNewNumber(dev, name, values, names, n);
1023 }
1024 
1026 {
1027  if (enable)
1028  {
1029  if (!isStreaming)
1030  {
1032 #if 0
1033  if (StreamOptionsN[OPTION_RATE_DIVISOR].value > 0)
1035  "Starting the video stream with target FPS %.f and rate divisor of %.f",
1036  StreamOptionsN[OPTION_TARGET_FPS].value, StreamOptionsN[OPTION_RATE_DIVISOR].value);
1037  else
1038  LOGF_INFO("Starting the video stream with target FPS %.f", StreamOptionsN[OPTION_TARGET_FPS].value);
1039 #endif
1040  LOGF_INFO("Starting the video stream with target exposure %.6f s (Max theoritical FPS %.f)",
1041  StreamExposureNP[0].getValue(), 1 / StreamExposureNP[0].getValue());
1042 
1043  FPSAverage.reset();
1044  FPSFast.reset();
1045  FPSPreview.reset();
1046  FPSPreview.setTimeWindow(1000.0 / LimitsNP[LIMITS_PREVIEW_FPS].getValue());
1047  frameCountDivider = 0;
1048 
1050  {
1051  if (dynamic_cast<INDI::CCD*>(currentDevice)->StartStreaming() == false)
1052  {
1053  StreamSP.reset();
1054  StreamSP[1].setState(ISS_ON);
1056  LOG_ERROR("Failed to start streaming.");
1057  StreamSP.apply();
1058  return false;
1059  }
1060  }
1062  {
1063  if (dynamic_cast<INDI::SensorInterface*>(currentDevice)->StartStreaming() == false)
1064  {
1065  StreamSP.reset();
1066  StreamSP[1].setState(ISS_ON);
1068  LOG_ERROR("Failed to start streaming.");
1069  StreamSP.apply();
1070  return false;
1071  }
1072  }
1073  isStreaming = true;
1074  Format.clear();
1075  FpsNP[FPS_INSTANT].setValue(0);
1076  FpsNP[FPS_AVERAGE].setValue(0);
1077  StreamSP.reset();
1078  StreamSP[0].setState(ISS_ON);
1079  recorder->setStreamEnabled(true);
1080  }
1081  }
1082  else
1083  {
1085  Format.clear();
1086  FpsNP[FPS_INSTANT].setValue(0);
1087  FpsNP[FPS_AVERAGE].setValue(0);
1088  if (isStreaming)
1089  {
1090  if (!isRecording)
1091  {
1093  {
1094  if (dynamic_cast<INDI::CCD*>(currentDevice)->StopStreaming() == false)
1095  {
1097  LOG_ERROR("Failed to stop streaming.");
1098  StreamSP.apply();
1099  return false;
1100  }
1101  }
1103  {
1104  if (dynamic_cast<INDI::SensorInterface*>(currentDevice)->StopStreaming() == false)
1105  {
1107  LOG_ERROR("Failed to stop streaming.");
1108  StreamSP.apply();
1109  return false;
1110  }
1111  }
1112  }
1113 
1114  StreamSP.reset();
1115  StreamSP[1].setState(ISS_ON);
1116  isStreaming = false;
1117  Format.clear();
1118  FpsNP[FPS_INSTANT].setValue(0);
1119  FpsNP[FPS_AVERAGE].setValue(0);
1120 
1121  recorder->setStreamEnabled(false);
1122  }
1123  }
1124 
1125  StreamSP.apply();
1126  return true;
1127 }
1128 
1129 bool StreamManager::setStream(bool enable)
1130 {
1131  D_PTR(StreamManager);
1132  return d->setStream(enable);
1133 }
1134 
1136 {
1137  D_PTR(StreamManager);
1138  d->EncoderSP.save(fp);
1139  d->RecordFileTP.save(fp);
1140  d->RecordOptionsNP.save(fp);
1141  d->RecorderSP.save(fp);
1142  d->LimitsNP.save(fp);
1143  return true;
1144 }
1145 
1146 void StreamManagerPrivate::getStreamFrame(uint16_t * x, uint16_t * y, uint16_t * w, uint16_t * h) const
1147 {
1148  *x = StreamFrameNP[CCDChip::FRAME_X].getValue();
1149  *y = StreamFrameNP[CCDChip::FRAME_Y].getValue();
1150  *w = StreamFrameNP[CCDChip::FRAME_W].getValue();
1151  *h = StreamFrameNP[CCDChip::FRAME_H].getValue();
1152 }
1153 
1154 void StreamManagerPrivate::setStreamFrame(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
1155 {
1156  StreamFrameNP[CCDChip::FRAME_X].setValue(x);
1157  StreamFrameNP[CCDChip::FRAME_Y].setValue(y);
1158  StreamFrameNP[CCDChip::FRAME_W].setValue(w);
1159  StreamFrameNP[CCDChip::FRAME_H].setValue(h);
1160 }
1161 
1163 {
1164  setStreamFrame(frameInfo.x, frameInfo.y, frameInfo.w, frameInfo.h);
1165 }
1166 
1167 void StreamManager::getStreamFrame(uint16_t * x, uint16_t * y, uint16_t * w, uint16_t * h) const
1168 {
1169  D_PTR(const StreamManager);
1170  d->getStreamFrame(x, y, w, h);
1171 }
1172 
1173 bool StreamManagerPrivate::uploadStream(const uint8_t * buffer, uint32_t nbytes)
1174 {
1175  // Send as is, already encoded.
1176  if (PixelFormat == INDI_JPG)
1177  {
1178  // Upload to client now
1179 #ifdef HAVE_WEBSOCKET
1180  if (dynamic_cast<INDI::CCD*>(currentDevice)->HasWebSocket()
1181  && dynamic_cast<INDI::CCD*>(currentDevice)->WebSocketS[CCD::WEBSOCKET_ENABLED].s == ISS_ON)
1182  {
1183  if (Format != ".streajpg")
1184  {
1185  Format = ".streajpg";
1186  dynamic_cast<INDI::CCD*>(currentDevice)->wsServer.send_text(Format);
1187  }
1188 
1189  dynamic_cast<INDI::CCD*>(currentDevice)->wsServer.send_binary(buffer, nbytes);
1190  return true;
1191  }
1192 #endif
1193  imageBP[0].setBlob(const_cast<uint8_t *>(buffer));
1194  imageBP[0].setBlobLen(nbytes);
1195  imageBP[0].setSize(nbytes);
1196  imageBP[0].setFormat(".stream_jpg");
1198  imageBP.apply();
1199  return true;
1200  }
1201 
1202  // Binning for grayscale frames only for now - REMOVE ME
1203 #if 0
1204  if (dynamic_cast<INDI::CCD*>(currentDevice)->PrimaryCCD.getNAxis() == 2)
1205  {
1206  dynamic_cast<INDI::CCD*>(currentDevice)->PrimaryCCD.binFrame();
1207  nbytes /= dynamic_cast<INDI::CCD*>(currentDevice)->PrimaryCCD.getBinX() * dynamic_cast<INDI::CCD*>
1208  (currentDevice)->PrimaryCCD.getBinY();
1209  }
1210 #endif
1211 
1213  {
1214  if (encoder->upload(&imageBP[0], buffer, nbytes, dynamic_cast<INDI::CCD*>(currentDevice)->PrimaryCCD.isCompressed()))
1215  {
1216 #ifdef HAVE_WEBSOCKET
1217  if (dynamic_cast<INDI::CCD*>(currentDevice)->HasWebSocket()
1218  && dynamic_cast<INDI::CCD*>(currentDevice)->WebSocketS[CCD::WEBSOCKET_ENABLED].s == ISS_ON)
1219  {
1220  if (Format != ".stream")
1221  {
1222  Format = ".stream";
1223  dynamic_cast<INDI::CCD*>(currentDevice)->wsServer.send_text(Format);
1224  }
1225 
1226  dynamic_cast<INDI::CCD*>(currentDevice)->wsServer.send_binary(buffer, nbytes);
1227  return true;
1228  }
1229 #endif
1230  // Upload to client now
1232  imageBP.apply();
1233  return true;
1234  }
1235  }
1237  {
1238  if (encoder->upload(&imageBP[0], buffer, nbytes,
1239  false))//dynamic_cast<INDI::SensorInterface*>(currentDevice)->isCompressed()))
1240  {
1241  // Upload to client now
1243  imageBP.apply();
1244  return true;
1245  }
1246  }
1247 
1248  return false;
1249 }
1250 
1252 {
1253  D_PTR(const StreamManager);
1254  return d->recorder;
1255 }
1256 
1258 {
1259  D_PTR(const StreamManager);
1260  return d->direct_record;
1261 }
1262 
1264 {
1265  D_PTR(const StreamManager);
1266  return d->isStreaming;
1267 }
1268 
1270 {
1271  D_PTR(const StreamManager);
1272  return d->isRecording && !d->isRecordingAboutToClose;
1273 }
1274 
1276 {
1277  D_PTR(const StreamManager);
1278  return (d->isStreaming || d->isRecording);
1279 }
1280 
1282 {
1283  D_PTR(const StreamManager);
1284  return 1.0 / d->StreamExposureNP[0].getValue();
1285 }
1287 {
1288  D_PTR(const StreamManager);
1289  return d->StreamExposureNP[0].getValue();
1290 }
1291 
1293 {
1294  D_PTR(StreamManager);
1295  d->hasStreamingExposure = enable;
1296 }
1297 
1298 }
void apply(const uint16_t *source, size_t count, uint8_t *destination) const
Definition: gammalut16.cpp:43
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
INDI::PropertyBlob getBLOB(const char *name) const
Definition: basedevice.cpp:109
bool isCompressed() const
isCompressed
Definition: indiccdchip.h:231
int getNAxis() const
Class to provide general functionality of CCD cameras with a single CCD sensor, or a primary CCD sens...
Definition: indiccd.h:116
CCDChip PrimaryCCD
Definition: indiccd.h:629
bool HasWebSocket()
Definition: indiccd.h:265
int CurrentFilterSlot
Definition: indiccd.h:623
@ WEBSOCKET_ENABLED
Definition: indiccd.h:740
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint16_t getDriverInterface() const
The ElapsedTimer class provides a fast way to calculate elapsed times.
int64_t nsecsElapsed() const
Returns the number of nanoseconds since this ElapsedTimer was last started.
void start()
Starts this timer. Once started, a timer value can be checked with elapsed().
The EncoderInterface class is the base class for video streaming encoders.
virtual void init(INDI::DefaultDevice *mainDevice)
virtual bool setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth)
virtual bool upload(INDI::WidgetViewBlob *bp, const uint8_t *buffer, uint32_t nbytes, bool isCompressed=false)=0
void setEncoder(EncoderInterface *encoder)
EncoderInterface * getDefaultEncoder()
std::vector< EncoderInterface * > getEncoderList()
void reset()
Reset all frame information.
Definition: fpsmeter.cpp:80
void setTimeWindow(double timeWindow)
Time window setup.
Definition: fpsmeter.cpp:55
double deltaTime() const
Time in milliseconds between last frames.
Definition: fpsmeter.cpp:65
double totalTime() const
Total time.
Definition: fpsmeter.cpp:75
uint64_t totalFrames() const
Total frames.
Definition: fpsmeter.cpp:70
double framesPerSecond() const
Number of frames per second counted in the time window.
Definition: fpsmeter.cpp:60
bool newFrame()
When you get a frame, call the function to count.
Definition: fpsmeter.cpp:31
void setState(IPState state)
WidgetView< T > * findWidgetByName(const char *name) const
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
IPState getState() const
void resize(size_t size)
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
INDI::WidgetViewSwitch * findOnSwitch() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
The RecorderInterface class is the base class for recorders.
virtual bool writeFrame(const uint8_t *frame, uint32_t nbytes, uint64_t timestamp)=0
virtual const char * getName()
virtual bool open(const char *filename, char *errmsg)=0
virtual bool close()=0
virtual const char * getExtension()=0
virtual bool setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth=8)=0
virtual bool setFPS(float FPS)
virtual void setStreamEnabled(bool enable)=0
std::vector< RecorderInterface * > getRecorderList()
RecorderInterface * getDefaultRecorder()
void setRecorder(RecorderInterface *recorder)
The SensorDevice class provides functionality of a Sensor Device within a Sensor.
void start(const std::function< void(const std::atomic_bool &isAboutToClose)> &functionToRun)
Reserves a thread and uses it to run functionToRun. A running function can check the 'isAboutToClose'...
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
std::atomic< bool > isRecording
bool setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth)
INDI::PropertySwitch RecordStreamSP
static std::string expand(const std::string &fname, const std::map< std::string, std::string > &patterns)
void setStreamFrame(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
INDI::PropertySwitch EncoderSP
std::atomic< bool > isStreaming
INDI::PropertySwitch StreamSP
static void subframe(const uint8_t *srcBuffer, const FrameInfo &srcFrameInfo, uint8_t *dstBuffer, const FrameInfo &dstFrameInfo)
void newFrame(const uint8_t *buffer, uint32_t nbytes, uint64_t timestamp)
std::atomic< bool > isRecordingAboutToClose
bool stopRecording(bool force=false)
UniqueQueue< TimeFrame > framesIncoming
INDI::PropertyBlob imageBP
std::atomic< bool > framesThreadTerminate
INDI::PropertyNumber RecordOptionsNP
EncoderInterface * encoder
const char * getDeviceName() const
INDI::PropertyNumber StreamTimeNP
INDI::PropertyNumber FpsNP
void setSize(uint16_t width, uint16_t height)
bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
bool uploadStream(const uint8_t *buffer, uint32_t nbytes)
uploadStream Upload frame to client using the selected encoder
INDI_PIXEL_FORMAT PixelFormat
RecorderManager recorderManager
bool recordStream(const uint8_t *buffer, uint32_t nbytes, double deltams, uint64_t timestamp)
recordStream Calls the backend recorder to record a single frame.
RecorderInterface * recorder
INDI::PropertyText RecordFileTP
INDI::PropertyNumber StreamFrameNP
INDI::PropertyNumber LimitsNP
StreamManagerPrivate(DefaultDevice *defaultDevice)
void getStreamFrame(uint16_t *x, uint16_t *y, uint16_t *w, uint16_t *h) const
void asyncStreamThread()
Thread processing frames and forwarding to recording and preview.
void ISGetProperties(const char *dev)
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
INDI::PropertyNumber StreamExposureNP
INDI::PropertySwitch RecorderSP
void setSize(uint16_t width, uint16_t height=1)
bool isDirectRecording() const
void getStreamFrame(uint16_t *x, uint16_t *y, uint16_t *w, uint16_t *h) const
void newFrame(const uint8_t *buffer, uint32_t nbytes, uint64_t timestamp=0)
newFrame CCD drivers call this function when a new frame is received. It is then streamed,...
void setStreamingExposureEnabled(bool enable)
setStreamingExposureEnabled Can stream exposure time be changed?
bool setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth=8)
double getTargetExposure() const
bool isStreaming() const
bool setStream(bool enable)
setStream Enables (starts) or disables (stops) streaming.
double getTargetFPS() const
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
virtual void ISGetProperties(const char *dev)
virtual bool initProperties()
StreamManager(DefaultDevice *currentDevice)
bool isRecording() const
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
virtual bool saveConfigItems(FILE *fp)
RecorderInterface * getRecorder() const
virtual bool updateProperties()
const char * getDeviceName() const
int errno
double min(void)
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
INDI_PIXEL_FORMAT
Definition: indibasetypes.h:70
@ INDI_RGB
Definition: indibasetypes.h:80
@ INDI_JPG
Definition: indibasetypes.h:82
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define DEBUGF(priority, msg,...)
Definition: indilogger.h:57
#define MAXRBUF
Definition: indiserver.cpp:102
std::vector< uint8_t > buffer
Namespace to encapsulate INDI client, drivers, and mediator classes.
std::string format_time(const std::tm &tm, const char *format)
Definition: indiutility.cpp:86
void replace_all(std::string &subject, const std::string &search, const std::string &replace)
Definition: indiutility.cpp:95
int mkpath(std::string s, mode_t mode)
Definition: indiutility.cpp:51
const char * getName() const