Torc  0.1
torccameraoutput.cpp
Go to the documentation of this file.
1 /* Class TorcCameraOutput
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2018
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 #define AKAMAI_TIME_ISO QStringLiteral("http://time.akamai.com/?iso")
24 
25 // Qt
26 #include <QDir>
27 
28 // Torc
29 #include "torclogging.h"
30 #include "torcmime.h"
31 #include "torcdirectories.h"
32 #include "torcnetworkrequest.h"
33 #include "torcoutputs.h"
34 #include "torccamerathread.h"
35 #include "torccameraoutput.h"
36 
37 TorcCameraOutput::TorcCameraOutput(TorcOutput::Type Type, double Value, const QString &ModelId, const QVariantMap &Details,
38  QObject *Output, const QMetaObject &MetaObject, const QString &Blacklist)
39  : TorcOutput(Type, Value, ModelId, Details, Output, MetaObject, Blacklist + "," + "CameraErrored,ParamsChanged"),
40  m_thread(nullptr),
41  m_threadLock(QReadWriteLock::Recursive),
42  m_params(Details),
43  m_paramsLock(QReadWriteLock::Recursive),
44  m_cameraName()
45 {
46 }
47 
49 {
50  return m_params;
51 }
52 
54 {
55  m_params = Params;
56 }
57 
65 {
66  m_paramsLock.lockForWrite();
67  m_params = Params;
68  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Camera parameters changed - video codec '%1'").arg(m_params.m_videoCodec));
69  m_paramsLock.unlock();
70 }
71 
72 void TorcCameraOutput::Graph(QByteArray *Data)
73 {
74  if (!Data)
75  return;
76  TorcOutput::Graph(Data);
77  Data->append(QStringLiteral("\"%1\"->%2\r\n").arg(m_cameraName).arg(uniqueId));
78 }
79 
81 {
83 }
84 
85 TorcCameraStillsOutput::TorcCameraStillsOutput(const QString &ModelId, const QVariantMap &Details)
86  : TorcCameraOutput(TorcOutput::Camera, 0.0, ModelId, Details, this, TorcCameraStillsOutput::staticMetaObject,
87  QStringLiteral("StillReady")),
88  stillsList(),
89  m_stillsList(),
90  m_stillsDirectory()
91 {
92  m_stillsDirectory = GetTorcContentDir() + ModelId + "/";
93  m_params.m_contentDir = m_stillsDirectory;
94 
95  // populate m_stillsList - camera has not started yet, so should be static
96  QDir stillsdir(m_stillsDirectory);
97  if (!stillsdir.exists())
98  stillsdir.mkpath(m_stillsDirectory);
99  QStringList namefilters;
100  QStringList imagefilters = TorcMime::ExtensionsForType(QStringLiteral("image"));
101  foreach (const QString &image, imagefilters)
102  { namefilters << QStringLiteral("*.%1").arg(image); }
103  QFileInfoList stills = stillsdir.entryInfoList(namefilters, QDir::NoDotAndDotDot | QDir::Files | QDir::Readable, QDir::Name);
104  foreach (const QFileInfo &file, stills)
105  m_stillsList.append(file.fileName());
106 }
107 
109 {
110  Stop();
111 }
112 
114 {
115  m_threadLock.lockForWrite();
116  if (m_thread)
117  {
119  m_thread = nullptr;
120  }
121  m_threadLock.unlock();
122 }
123 
125 {
126  Stop();
127 
128  m_threadLock.lockForWrite();
129  m_paramsLock.lockForRead();
131  m_paramsLock.unlock();
132  if (m_thread)
133  {
134  m_thread->SetStillsParent(this);
135  m_thread->start();
136  // listen for requests
138  }
139  m_threadLock.unlock();
140 }
141 
143 {
144  QReadLocker locker(&m_threadLock);
145  return m_stillsList;
146 }
147 
149 {
150  return TorcOutput::Camera;
151 }
152 
154 {
155  SetValid(!Errored);
156  if (Errored)
157  {
158  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Camera reported error"));
159  Stop();
160  }
161 }
162 
163 void TorcCameraStillsOutput::StillReady(const QString &File)
164 {
165  QWriteLocker locker(&m_threadLock);
166  if (m_stillsList.contains(File))
167  {
168  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Still '%1' is duplicate - ignoring").arg(File));
169  }
170  else
171  {
172  m_stillsList.append(File);
173  emit StillsListChanged(m_stillsList);
174  }
175 }
176 
177 TorcCameraVideoOutput::TorcCameraVideoOutput(const QString &ModelId, const QVariantMap &Details)
178  : TorcCameraOutput(TorcOutput::Camera, 0.0, ModelId, Details, this, TorcCameraVideoOutput::staticMetaObject,
179  QStringLiteral("WritingStarted,WritingStopped,SegmentRemoved,InitSegmentReady,SegmentReady,TimeCheck,RequestReady")),
180  m_segments(),
181  m_segmentLock(QReadWriteLock::Recursive),
182  m_cameraStartTime(),
183  m_networkTimeAbort(0),
184  m_networkTimeRequest(nullptr)
185 {
186 }
187 
189 {
190  Stop();
191 
192  m_networkTimeAbort = 1;
193 
194  if (m_networkTimeRequest)
195  {
196  TorcNetwork::Cancel(m_networkTimeRequest);
197  m_networkTimeRequest->DownRef();
198  }
199 }
200 
202 {
203  if (m_networkTimeRequest)
204  {
205  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Time check in progress - not resending"));
206  return;
207  }
208 
209  QUrl url(AKAMAI_TIME_ISO);
210  QNetworkRequest request(url);
211  m_networkTimeRequest = new TorcNetworkRequest(request, QNetworkAccessManager::GetOperation, 0, &m_networkTimeAbort);
212  TorcNetwork::GetAsynchronous(m_networkTimeRequest, this);
213 }
214 
216 {
217  if (Request && m_networkTimeRequest && (Request == m_networkTimeRequest))
218  {
219  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Network time (%1) : %2").arg(AKAMAI_TIME_ISO, Request->GetBuffer().constData()));
220  m_networkTimeRequest->DownRef();
221  m_networkTimeRequest = nullptr;
222  }
223 }
224 
226 {
227  return TorcOutput::Camera;
228 }
229 
231 {
232  return QStringLiteral("%1%2").arg(m_signature, VIDEO_PAGE);
233 }
234 
236 {
237  Stop();
238 
240  emit CheckTime();
241 
242  m_threadLock.lockForWrite();
243  m_paramsLock.lockForRead();
245  m_paramsLock.unlock();
246  if (m_thread)
247  {
248  m_thread->SetVideoParent(this);
249  m_thread->start();
251  }
252  m_threadLock.unlock();
253 }
254 
257 {
258  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Initial segment ready"));
259 }
260 
263 {
264  // we actually deem video output to be valid once the init segment has been received
265  // (which is signalled separately)
266  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Camera started"));
267 }
268 
270 {
271  m_segmentLock.lockForWrite();
272  m_segments.clear();
273  m_segmentLock.unlock();
274 
275  m_threadLock.lockForWrite();
276  if (m_thread)
277  {
279  m_thread = nullptr;
280  }
281  m_threadLock.unlock();
282 }
283 
286 {
287  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Camera stopped"));
288  m_threadLock.lockForWrite();
289  m_cameraStartTime = QDateTime();
290  m_threadLock.unlock();
291 }
292 
294 {
295  SetValid(!Errored);
296  if (Errored)
297  {
298  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Camera reported error"));
299  Stop();
300  }
301 }
302 
304 {
305  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Segment %1 ready").arg(Segment));
306 
307  // allow remote clients to start reading once the first segment is saved
308  m_threadLock.lockForRead();
309  if (!m_cameraStartTime.isValid())
310  {
311  m_threadLock.unlock();
312  m_threadLock.lockForWrite();
313  // set start time now and 'backdate' as the init segment is usually sent once processing has started, so the rest
314  // of the segment arrives quicker than 'expected'
315  m_cameraStartTime = QDateTime::currentDateTimeUtc().addMSecs(VIDEO_SEGMENT_TARGET * -1000);
316  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("First segment ready - start time set"));
317  }
318  m_threadLock.unlock();
319 
320  QWriteLocker locker(&m_segmentLock);
321  if (!m_segments.contains(Segment))
322  {
323  if (!m_segments.isEmpty() && m_segments.first() >= Segment) //clazy:exclude=detaching-member
324  {
325  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Segment %1 is not greater than head (%2)").arg(Segment).arg(m_segments.first())); //clazy:exclude=detaching-member
326  }
327  else
328  {
329  m_segments.enqueue(Segment);
330  }
331  }
332  else
333  {
334  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Already have a segment #%1").arg(Segment));
335  }
336 }
337 
339 {
340  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Segment %1 removed").arg(Segment));
341 
342  QWriteLocker locker(&m_segmentLock);
343 
344  if (m_segments.isEmpty())
345  {
346  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Cannot remove segment - segments list is empty"));
347  }
348  else
349  {
350  if (m_segments.first() != Segment) //clazy:exclude=detaching-member
351  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Segment %1 is not at tail of queue").arg(Segment));
352  else
353  m_segments.dequeue();
354  }
355 }
356 
357 void TorcCameraVideoOutput::ProcessHTTPRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request)
358 {
359  (void)PeerAddress;
360  (void)PeerPort;
361  (void)LocalAddress;
362  (void)LocalPort;
363  (void)Request;
364 
365  // treat camera feeds as secure - or maybe not - many players just don't seem to expect authorisation
366  //if (!MethodIsAuthorised(Request, HTTPAuth))
367  // return;
368 
369  // cameraStartTime is set when the first segment has been received
370  m_threadLock.lockForRead();
371  bool valid = m_cameraStartTime.isValid();
372  m_threadLock.unlock();
373  if (!valid)
374  return;
375 
376  Request.SetAllowCORS(true); // needed for a number of browser players.
377  QString method = Request.GetMethod();
378  HTTPRequestType type = Request.GetHTTPRequestType();
379 
380  bool hlsmaster = method.compare(HLS_PLAYLIST_MAST) == 0;
381  bool hlsplaylist = method.compare(HLS_PLAYLIST) == 0;
382  bool player = method.compare(VIDEO_PAGE) == 0;
383  bool dash = method.compare(DASH_PLAYLIST) == 0;
384  bool segment = method.startsWith(QStringLiteral("segment")) && method.endsWith(QStringLiteral(".m4s"));
385  bool init = method.startsWith(QStringLiteral("init")) && method.endsWith(QStringLiteral(".mp4"));
386 
387  if (!(hlsplaylist || player || hlsmaster || dash || segment || init))
388  {
389  Request.SetStatus(HTTP_NotFound);
391  return;
392  }
393 
394  if (type == HTTPOptions)
395  {
397  if (segment || init)
399  else if (hlsmaster || hlsplaylist)
401  else if (player)
403  else
405  return;
406  }
407 
408  if (type != HTTPGet && type != HTTPHead)
409  {
410  Request.SetStatus(HTTP_BadRequest);
412  return;
413  }
414 
415  QByteArray result;
416 
417  if (hlsmaster)
418  {
419  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Sending master HLS playlist"));
420  result = GetMasterPlaylist();
421  Request.SetAllowGZip(true);
423  }
424  else if (hlsplaylist)
425  {
426  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Sending HLS playlist"));
427  result = GetHLSPlaylist();
428  Request.SetAllowGZip(true);
430  }
431  else if (player)
432  {
433  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Sending video page"));
434  result = GetPlayerPage();
435  Request.SetAllowGZip(true);
437  }
438  else if (dash)
439  {
440  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Sending DASH playlist"));
441  result = GetDashPlaylist();
442  Request.SetAllowGZip(true);
444  }
445  else if (segment)
446  {
447  QString number = method.mid(7);
448  number.chop(4);
449  bool ok = false;
450  int num = number.toInt(&ok);
451  if (ok)
452  {
453  QReadLocker locker(&m_threadLock);
454  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Segment %1 requested").arg(num));
455  result = m_thread->GetSegment(num);
456  if (!result.isEmpty())
457  {
458  Request.SetAllowGZip(false);
460  }
461  else
462  {
463  QReadLocker locker(&m_segmentLock);
464  if (m_segments.isEmpty())
465  {
466  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("No segments - %1 requested").arg(num));
467  }
468  else
469  {
470  m_threadLock.lockForRead();
471  QDateTime start = m_cameraStartTime;
472  m_threadLock.unlock();
473  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Segment %1 not found - we have %2-%3").arg(num).arg(m_segments.first()).arg(m_segments.last())); //clazy:exclude=detaching-member
474  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Our start time: %1").arg(start.toString(Qt::ISODate)));
475  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Start+request : %1").arg(start.addSecs(num *2).toString(Qt::ISODate)));
476  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Start+first : %1").arg(start.addSecs(m_segments.first() * 2).toString(Qt::ISODate))); //clazy:exclude=detaching-member
477  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Start+last : %1").arg(start.addSecs(m_segments.last() * 2).toString(Qt::ISODate))); //clazy:exclude=detaching-member
478  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("System time : %1").arg(QDateTime::currentDateTimeUtc().toString(Qt::ISODate)));
479  emit CheckTime();
480  }
481 
482  }
483  }
484  else
485  {
486  Request.SetStatus(HTTP_BadRequest);
488  return;
489  }
490  }
491  else if (init)
492  {
493  QReadLocker locker(&m_threadLock);
494  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Init segment requested"));
495  result = m_thread->GetInitSegment();
496  if (!result.isEmpty())
497  {
498  Request.SetAllowGZip(false);
500  }
501  else
502  {
503  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Init segment not found"));
504  }
505  }
506 
507  if (!result.isEmpty())
508  {
509  Request.SetResponseContent(result);
510  Request.SetStatus(HTTP_OK);
511  }
512  else
513  {
514  Request.SetStatus(HTTP_NotFound);
516  }
517 }
518 
519 QByteArray TorcCameraVideoOutput::GetPlayerPage(void)
520 {
521  static const QString player("<html>\r\n"
522  " <script src=\"/js/vendor/dash-2.9.0.all.min.js\"></script>\r\n"
523  " <body>\r\n"
524  " <div>\r\n"
525  " <video data-dashjs-player autoplay src=\"%1\" controls></video>\r\n"
526  " </div>\r\n"
527  " </body>\r\n"
528  "</html>\r\n");
529 
530  return QByteArray(player.arg(DASH_PLAYLIST).toLocal8Bit());
531 }
532 
533 QByteArray TorcCameraVideoOutput::GetMasterPlaylist(void)
534 {
535  static const QString playlist("#EXTM3U\r\n"
536  "#EXT-X-VERSION:4\r\n"
537  "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1,RESOLUTION=%2x%3,CODECS=\"%5\"\r\n"
538  "%4\r\n");
539 
540  m_paramsLock.lockForRead();
541  QByteArray result(playlist.arg(m_params.m_bitrate).arg(m_params.m_width).arg(m_params.m_height)
542  .arg(HLS_PLAYLIST, m_params.m_videoCodec)/*.arg(AUDIO_CODEC_ISO)*/.toLocal8Bit());
543  m_paramsLock.unlock();
544  return result;
545 }
546 
547 QByteArray TorcCameraVideoOutput::GetHLSPlaylist(void)
548 {
549  static const QString playlist("#EXTM3U\r\n"
550  "#EXT-X-VERSION:4\r\n"
551  "#EXT-X-TARGETDURATION:%1\r\n"
552  "#EXT-X-MEDIA-SEQUENCE:%2\r\n"
553  "#EXT-X-MAP:URI=\"initSegment.mp4\"\r\n");
554 
555  m_paramsLock.lockForRead();
556  QString duration = QString::number(m_params.m_segmentLength / (float) m_params.m_frameRate, 'f', 2);
557  m_paramsLock.unlock();
558  m_segmentLock.lockForRead();
559  QString result = playlist.arg(duration).arg(m_segments.first()); //clazy:exclude=detaching-member
560  foreach (int segment, m_segments)
561  {
562  result += QStringLiteral("#EXTINF:%1,\r\n").arg(duration);
563  result += QStringLiteral("segment%1.m4s\r\n").arg(segment);
564  }
565  m_segmentLock.unlock();
566  return QByteArray(result.toLocal8Bit());
567 }
568 
569 QByteArray TorcCameraVideoOutput::GetDashPlaylist(void)
570 {
571  static const QString dash(
572  "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n"
573  "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\""
574  " type=\"dynamic\" availabilityStartTime=\"%1\" minimumUpdatePeriod=\"PT%2S\""
575  " publishTime=\"%1\" timeShiftBufferDepth=\"PT%3S\" minBufferTime=\"PT%4S\">\r\n"
576  " <Period id=\"0\" start=\"PT0S\">\r\n"
577  " <AdaptationSet contentType=\"video\" segmentAlignment=\"true\" startWithSAP=\"1\">\r\n"
578  " <Representation id=\"default\" mimeType=\"%5\" width=\"%6\" height=\"%7\" bandwidth=\"%8\" codecs=\"%9\" frameRate=\"%10\">\r\n"
579  " <SegmentTemplate duration=\"%11\" timescale=\"%12\" initialization=\"initSegment.mp4\" media=\"segment$Number$.m4s\" startNumber=\"0\" />\r\n"
580  " </Representation>\r\n"
581  " </AdaptationSet>\r\n"
582  " </Period>\r\n"
583  "</MPD>\r\n");
584 
585  m_threadLock.lockForRead();
586  QString start = m_cameraStartTime.toString(Qt::ISODate);
587  m_threadLock.unlock();
588 
589  m_paramsLock.lockForRead();
590  double duration = m_params.m_segmentLength / (float)m_params.m_frameRate;
591  QByteArray result(dash.arg(start,
592  QString::number(duration * 5, 'f', 2),
593  QString::number(duration * 4, 'f', 2),
594  QString::number(duration * 2, 'f', 2))
595  .arg(TorcHTTPRequest::ResponseTypeToString(HTTPResponseMP4/*HTTPResponseMPEGTS*/),
596  QString::number(m_params.m_width),
597  QString::number(m_params.m_height),
598  QString::number(m_params.m_bitrate),
600  QString::number(m_params.m_frameRate),
601  QString::number(duration * m_params.m_timebase),
602  QString::number(m_params.m_timebase)).toLocal8Bit());
603  m_paramsLock.unlock();
604  return result;
605 }
606 
608 
610  : TorcDeviceHandler(),
611  m_cameras()
612 {
613 }
614 
615 void TorcCameraOutputs::Create(const QVariantMap &Details)
616 {
617  QWriteLocker locker(&m_handlerLock);
618 
619  QVariantMap::const_iterator ii = Details.constBegin();
620  for ( ; ii != Details.constEnd(); ++ii)
621  {
622  if (ii.key() != QStringLiteral("outputs"))
623  continue;
624 
625  QVariantMap outputs = ii.value().toMap();
626  QVariantMap::const_iterator i = outputs.constBegin();
627  for ( ; i != outputs.constEnd(); ++i)
628  {
629  if (i.key() != QStringLiteral("cameras"))
630  continue;
631 
632  QVariantMap cameras = i.value().toMap();
633  QVariantMap::iterator it = cameras.begin();
634  for ( ; it != cameras.end(); ++it)
635  {
636  QVariantMap types = it.value().toMap();
637  QVariantMap::iterator it2 = types.begin();
638  for ( ; it2 != types.end(); ++it2)
639  {
640  bool video = false;
641  bool stills = false;
642  QVariantMap camera;
643  if (it2.key() == QStringLiteral("video"))
644  {
645  video = true;
646  camera = it2.value().toMap();
647  }
648  else if (it2.key() == QStringLiteral("stills"))
649  {
650  stills = true;
651  camera = it2.value().toMap();
652  }
653  else
654  {
655  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown camera interface '%1'").arg(it2.key()));
656  continue;
657  }
658 
659  if (!camera.contains(QStringLiteral("name")))
660  {
661  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Camera '%1' has no name").arg(it.key()));
662  continue;
663  }
664  if (!camera.contains(QStringLiteral("width")))
665  {
666  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Camera '%1' does not specify width").arg(camera.value(QStringLiteral("name")).toString()));
667  continue;
668  }
669  if (!camera.contains(QStringLiteral("height")))
670  {
671  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Camera '%1' does not specify height").arg(camera.value(QStringLiteral("name")).toString()));
672  continue;
673  }
674  bool bitrate = camera.contains(QStringLiteral("bitrate"));
675  bool framerate = camera.contains(QStringLiteral("framerate"));
676 
677  if (video && !bitrate)
678  {
679  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Camera video interface '%1' does not specify bitrate").arg(camera.value(QStringLiteral("name")).toString()));
680  continue;
681  }
682 
683  if (video && !framerate)
684  {
685  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Camera video interface '%1' does not specify framerate").arg(camera.value(QStringLiteral("name")).toString()));
686  continue;
687  }
688 
689  // NB TorcCameraFactory checks that the underlying class can handle the specified camera
690  // which ensures the TorcCameraOutput instance will be able to create the camera.
691  TorcCameraOutput *newcamera = nullptr;
693  TorcCameraParams params(Details);
694  for ( ; factory; factory = factory->NextFactory())
695  {
696  if (factory->CanHandle(it.key(), params))
697  {
698  if (video)
699  newcamera = new TorcCameraVideoOutput(it.key(), camera);
700  if (stills)
701  newcamera = new TorcCameraStillsOutput(it.key(), camera);
702 
703  if (newcamera)
704  {
705  newcamera->SetCameraName(factory->GetCameraName());
706  m_cameras.insertMulti(it.key(), newcamera);
707  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("New '%1' camera '%2'").arg(it.key(), newcamera->GetUniqueId()));
708  break;
709  }
710  }
711  }
712  if (nullptr == newcamera)
713  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Failed to find handler for camera '%1'").arg(it.key()));
714  }
715  }
716  }
717  }
718 
719  // we now need to analyse our list of cameras and combine the parameters for output devices that
720  // reference the same camera device
721  QList<QString> keys = m_cameras.uniqueKeys();
722  foreach (const QString &key, keys)
723  {
724  QList<TorcCameraOutput*> outputs = m_cameras.values(key);
725  TorcCameraParams params;
726  foreach (TorcCameraOutput* output, outputs)
727  params = params.Combine(output->GetParams());
728  foreach (TorcCameraOutput* output, outputs)
729  output->SetParams(params);
730  }
731 }
732 
734 {
735  QWriteLocker locker(&m_handlerLock);
736  QHash<QString, TorcCameraOutput*>::const_iterator it = m_cameras.constBegin();
737  for ( ; it != m_cameras.constEnd(); ++it)
738  {
739  TorcOutputs::gOutputs->RemoveOutput(it.value());
740  it.value()->DownRef();
741  }
742  m_cameras.clear();
743 }
static TorcCameraOutputs * gCameraOutputs
virtual void Graph(QByteArray *Data)
Definition: torcoutput.cpp:92
static TorcCameraFactory * GetTorcCameraFactory(void)
Definition: torccamera.cpp:437
A class to encapsulate an incoming HTTP request.
void Destroy(void) override
TorcCameraParams & GetParams(void)
HTTPRequestType GetHTTPRequestType(void) const
#define AKAMAI_TIME_ISO
void ValueChanged(double Value)
QString m_videoCodec
Definition: torccamera.h:58
void Stop(void) override
Stop the device.
#define HLS_PLAYLIST_MAST
void Start(void) override
TorcOutput::Type GetType(void) override
void ParamsChanged(TorcCameraParams &Params)
Notify the output that the camera parameters have changed.
void CameraErrored(bool Errored) override
QStringList GetStillsList(void)
virtual bool CanHandle(const QString &Type, const TorcCameraParams &Params)=0
TorcCameraStillsOutput(const QString &ModelId, const QVariantMap &Details)
QByteArray GetSegment(int Segment)
QString uniqueId
Definition: torcdevice.h:63
virtual void SetValid(bool Valid)
Definition: torcdevice.cpp:101
TorcCameraFactory * NextFactory(void) const
Definition: torccamera.cpp:442
void InitSegmentReady(void)
The &#39;init&#39; segment is ready and available.
A wrapper around QNetworkRequest.
static bool GetAsynchronous(TorcNetworkRequest *Request, QObject *Parent)
Queue an asynchronous HTTP request.
Definition: torcnetwork.cpp:94
#define DASH_PLAYLIST
static QString ResponseTypeToString(HTTPResponseType Response)
virtual QString GetCameraName(void)=0
QString m_contentDir
Definition: torccamera.h:59
void WritingStopped(void)
The camera is no longer recording any video.
virtual bool DownRef(void)
void SetAllowCORS(bool Allowed)
void Graph(QByteArray *Data) override
void StillReady(const QString &File)
QByteArray & GetBuffer(void)
void StreamVideo(bool Video)
QReadWriteLock m_threadLock
void SetVideoParent(TorcCameraVideoOutput *Parent)
Connect camera signals for video streaming to the stream output device.
QReadWriteLock m_paramsLock
#define VIDEO_SEGMENT_TARGET
Definition: torccamera.h:24
QByteArray GetInitSegment(void)
void Create(const QVariantMap &Details) override
void SetCameraName(const QString &Name)
static void HandleOptions(TorcHTTPRequest &Request, int Allowed)
void Start(void) override
void SetResponseContent(const QByteArray &Content)
void SetParams(TorcCameraParams &Params)
QString GetMethod(void) const
QString Name(void) const
void SetAllowGZip(bool Allowed)
Allow gzip compression for the contents of this request.
TorcCameraVideoOutput(const QString &ModelId, const QVariantMap &Details)
void RequestReady(TorcNetworkRequest *Request)
void Stop(void) override
Stop the device.
QString GetTorcContentDir(void)
brief Return the path to generated content
TorcCameraThread * m_thread
void SegmentReady(int Segment)
QString modelId
Definition: torcdevice.h:62
void WritingStarted(void)
The camera has started to record video.
void SetStatus(HTTPStatus Status)
#define HLS_PLAYLIST
void StillsListChanged(QStringList &List)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
TorcOutput::Type GetType(void) override
QReadWriteLock m_handlerLock
void CameraErrored(bool Errored) override
HTTPRequestType
static QStringList ExtensionsForType(const QString &Type)
Returns a list of known file extensions for a given top level MIME type.
Definition: torcmime.cpp:101
static void Cancel(TorcNetworkRequest *Request)
Definition: torcnetwork.cpp:73
void SetResponseType(HTTPResponseType Type)
TorcCameraParams Combine(const TorcCameraParams &Add)
Definition: torccamera.cpp:189
QString GetPresentationURL(void) override
TorcCameraOutput(TorcOutput::Type Type, double Value, const QString &ModelId, const QVariantMap &Details, QObject *Output, const QMetaObject &MetaObject, const QString &Blacklist=QStringLiteral(""))
void TakeStills(uint Count)
void ProcessHTTPRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request) override
TorcCameraParams m_params
#define VIDEO_PAGE
static void CreateOrDestroy(TorcCameraThread *&Thread, const QString &Type, const TorcCameraParams &Params=TorcCameraParams())
Create and release shared camera threads/devices.
friend class TorcCameraOutputs
void SegmentRemoved(int Segment)
bool valid
Definition: torcdevice.h:24
void SetStillsParent(TorcCameraStillsOutput *Parent)
Connect camera signals for stills capture.
QString GetUniqueId(void)
Definition: torcdevice.cpp:162