23 #define AKAMAI_TIME_ISO QStringLiteral("http://time.akamai.com/?iso") 38 QObject *Output,
const QMetaObject &MetaObject,
const QString &Blacklist)
39 :
TorcOutput(Type, Value, ModelId, Details, Output, MetaObject, Blacklist +
"," +
"CameraErrored,ParamsChanged"),
41 m_threadLock(QReadWriteLock::Recursive),
43 m_paramsLock(QReadWriteLock::Recursive),
68 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Camera parameters changed - video codec '%1'").arg(
m_params.
m_videoCodec));
87 QStringLiteral(
"StillReady")),
96 QDir stillsdir(m_stillsDirectory);
97 if (!stillsdir.exists())
98 stillsdir.mkpath(m_stillsDirectory);
99 QStringList namefilters;
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());
158 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Camera reported error"));
166 if (m_stillsList.contains(File))
168 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Still '%1' is duplicate - ignoring").arg(File));
172 m_stillsList.append(File);
179 QStringLiteral(
"WritingStarted,WritingStopped,SegmentRemoved,InitSegmentReady,SegmentReady,TimeCheck,RequestReady")),
181 m_segmentLock(QReadWriteLock::Recursive),
183 m_networkTimeAbort(0),
184 m_networkTimeRequest(nullptr)
192 m_networkTimeAbort = 1;
194 if (m_networkTimeRequest)
197 m_networkTimeRequest->
DownRef();
203 if (m_networkTimeRequest)
205 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Time check in progress - not resending"));
210 QNetworkRequest request(url);
211 m_networkTimeRequest =
new TorcNetworkRequest(request, QNetworkAccessManager::GetOperation, 0, &m_networkTimeAbort);
217 if (Request && m_networkTimeRequest && (Request == m_networkTimeRequest))
220 m_networkTimeRequest->
DownRef();
221 m_networkTimeRequest =
nullptr;
258 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Initial segment ready"));
266 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Camera started"));
271 m_segmentLock.lockForWrite();
273 m_segmentLock.unlock();
287 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Camera stopped"));
289 m_cameraStartTime = QDateTime();
298 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Camera reported error"));
305 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Segment %1 ready").arg(Segment));
309 if (!m_cameraStartTime.isValid())
316 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"First segment ready - start time set"));
320 QWriteLocker locker(&m_segmentLock);
321 if (!m_segments.contains(Segment))
323 if (!m_segments.isEmpty() && m_segments.first() >= Segment)
325 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Segment %1 is not greater than head (%2)").arg(Segment).arg(m_segments.first()));
329 m_segments.enqueue(Segment);
334 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Already have a segment #%1").arg(Segment));
340 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Segment %1 removed").arg(Segment));
342 QWriteLocker locker(&m_segmentLock);
344 if (m_segments.isEmpty())
346 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Cannot remove segment - segments list is empty"));
350 if (m_segments.first() != Segment)
351 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Segment %1 is not at tail of queue").arg(Segment));
353 m_segments.dequeue();
371 bool valid = m_cameraStartTime.isValid();
382 bool player = method.compare(
VIDEO_PAGE) == 0;
384 bool segment = method.startsWith(QStringLiteral(
"segment")) && method.endsWith(QStringLiteral(
".m4s"));
385 bool init = method.startsWith(QStringLiteral(
"init")) && method.endsWith(QStringLiteral(
".mp4"));
387 if (!(hlsplaylist || player || hlsmaster || dash || segment || init))
399 else if (hlsmaster || hlsplaylist)
419 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Sending master HLS playlist"));
420 result = GetMasterPlaylist();
424 else if (hlsplaylist)
426 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Sending HLS playlist"));
427 result = GetHLSPlaylist();
433 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Sending video page"));
434 result = GetPlayerPage();
440 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Sending DASH playlist"));
441 result = GetDashPlaylist();
447 QString number = method.mid(7);
450 int num = number.toInt(&ok);
454 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Segment %1 requested").arg(num));
456 if (!result.isEmpty())
463 QReadLocker locker(&m_segmentLock);
464 if (m_segments.isEmpty())
466 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"No segments - %1 requested").arg(num));
471 QDateTime start = m_cameraStartTime;
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()));
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)));
477 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Start+last : %1").arg(start.addSecs(m_segments.last() * 2).toString(Qt::ISODate)));
478 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"System time : %1").arg(QDateTime::currentDateTimeUtc().toString(Qt::ISODate)));
494 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Init segment requested"));
496 if (!result.isEmpty())
503 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Init segment not found"));
507 if (!result.isEmpty())
519 QByteArray TorcCameraVideoOutput::GetPlayerPage(
void)
521 static const QString player(
"<html>\r\n" 522 " <script src=\"/js/vendor/dash-2.9.0.all.min.js\"></script>\r\n" 525 " <video data-dashjs-player autoplay src=\"%1\" controls></video>\r\n" 533 QByteArray TorcCameraVideoOutput::GetMasterPlaylist(
void)
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" 547 QByteArray TorcCameraVideoOutput::GetHLSPlaylist(
void)
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");
558 m_segmentLock.lockForRead();
559 QString result = playlist.arg(duration).arg(m_segments.first());
560 foreach (
int segment, m_segments)
562 result += QStringLiteral(
"#EXTINF:%1,\r\n").arg(duration);
563 result += QStringLiteral(
"segment%1.m4s\r\n").arg(segment);
565 m_segmentLock.unlock();
566 return QByteArray(result.toLocal8Bit());
569 QByteArray TorcCameraVideoOutput::GetDashPlaylist(
void)
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" 586 QString start = m_cameraStartTime.toString(Qt::ISODate);
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))
619 QVariantMap::const_iterator ii = Details.constBegin();
620 for ( ; ii != Details.constEnd(); ++ii)
622 if (ii.key() != QStringLiteral(
"outputs"))
625 QVariantMap outputs = ii.value().toMap();
626 QVariantMap::const_iterator i = outputs.constBegin();
627 for ( ; i != outputs.constEnd(); ++i)
629 if (i.key() != QStringLiteral(
"cameras"))
632 QVariantMap cameras = i.value().toMap();
633 QVariantMap::iterator it = cameras.begin();
634 for ( ; it != cameras.end(); ++it)
636 QVariantMap types = it.value().toMap();
637 QVariantMap::iterator it2 = types.begin();
638 for ( ; it2 != types.end(); ++it2)
643 if (it2.key() == QStringLiteral(
"video"))
646 camera = it2.value().toMap();
648 else if (it2.key() == QStringLiteral(
"stills"))
651 camera = it2.value().toMap();
655 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Unknown camera interface '%1'").arg(it2.key()));
659 if (!camera.contains(QStringLiteral(
"name")))
661 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Camera '%1' has no name").arg(it.key()));
664 if (!camera.contains(QStringLiteral(
"width")))
666 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Camera '%1' does not specify width").arg(camera.value(QStringLiteral(
"name")).toString()));
669 if (!camera.contains(QStringLiteral(
"height")))
671 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Camera '%1' does not specify height").arg(camera.value(QStringLiteral(
"name")).toString()));
674 bool bitrate = camera.contains(QStringLiteral(
"bitrate"));
675 bool framerate = camera.contains(QStringLiteral(
"framerate"));
677 if (video && !bitrate)
679 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Camera video interface '%1' does not specify bitrate").arg(camera.value(QStringLiteral(
"name")).toString()));
683 if (video && !framerate)
685 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Camera video interface '%1' does not specify framerate").arg(camera.value(QStringLiteral(
"name")).toString()));
696 if (factory->
CanHandle(it.key(), params))
706 m_cameras.insertMulti(it.key(), newcamera);
707 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"New '%1' camera '%2'").arg(it.key(), newcamera->
GetUniqueId()));
712 if (
nullptr == newcamera)
713 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Failed to find handler for camera '%1'").arg(it.key()));
721 QList<QString> keys = m_cameras.uniqueKeys();
722 foreach (
const QString &key, keys)
724 QList<TorcCameraOutput*> outputs = m_cameras.values(key);
736 QHash<QString, TorcCameraOutput*>::const_iterator it = m_cameras.constBegin();
737 for ( ; it != m_cameras.constEnd(); ++it)
739 TorcOutputs::gOutputs->RemoveOutput(it.value());
740 it.value()->DownRef();
static TorcCameraOutputs * gCameraOutputs
virtual void Graph(QByteArray *Data)
static TorcCameraFactory * GetTorcCameraFactory(void)
A class to encapsulate an incoming HTTP request.
void Destroy(void) override
TorcCameraParams & GetParams(void)
HTTPRequestType GetHTTPRequestType(void) const
void ValueChanged(double Value)
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)
virtual void SetValid(bool Valid)
TorcCameraFactory * NextFactory(void) const
void InitSegmentReady(void)
The 'init' segment is ready and available.
A wrapper around QNetworkRequest.
static bool GetAsynchronous(TorcNetworkRequest *Request, QObject *Parent)
Queue an asynchronous HTTP request.
static QString ResponseTypeToString(HTTPResponseType Response)
virtual QString GetCameraName(void)=0
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
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
void SetAllowGZip(bool Allowed)
Allow gzip compression for the contents of this request.
TorcCameraVideoOutput(const QString &ModelId, const QVariantMap &Details)
virtual ~TorcCameraVideoOutput()
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)
void WritingStarted(void)
The camera has started to record video.
void SetStatus(HTTPStatus Status)
void StillsListChanged(QStringList &List)
#define LOG(_MASK_, _LEVEL_, _STRING_)
TorcOutput::Type GetType(void) override
QReadWriteLock m_handlerLock
void CameraErrored(bool Errored) override
static QStringList ExtensionsForType(const QString &Type)
Returns a list of known file extensions for a given top level MIME type.
static void Cancel(TorcNetworkRequest *Request)
void SetResponseType(HTTPResponseType Type)
TorcCameraParams Combine(const TorcCameraParams &Add)
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
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)
virtual ~TorcCameraStillsOutput()
void SetStillsParent(TorcCameraStillsOutput *Parent)
Connect camera signals for stills capture.
QString GetUniqueId(void)