Torc  0.1
torccamera.cpp
Go to the documentation of this file.
1 /* Class TorcCamera
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 // Qt
24 #include <QFile>
25 
26 // Torc
27 #include "torclogging.h"
28 #include "torccameraoutput.h"
29 #include "torccamera.h"
30 
32  : m_valid(false),
33  m_width(0),
34  m_height(0),
35  m_stride(0),
36  m_sliceHeight(0),
37  m_frameRate(1),
38  m_bitrate(0),
39  m_timebase(VIDEO_TIMEBASE),
40  m_segmentLength(0),
41  m_gopSize(0),
42  m_videoCodec(),
43  m_contentDir()
44 {
45 }
46 
47 TorcCameraParams::TorcCameraParams(const QVariantMap &Details)
48  : m_valid(false),
49  m_width(0),
50  m_height(0),
51  m_stride(0),
52  m_sliceHeight(0),
53  m_frameRate(1),
54  m_bitrate(0),
56  m_segmentLength(0),
57  m_gopSize(0),
58  m_videoCodec(),
59  m_contentDir()
60 {
61  if (!Details.contains(QStringLiteral("width")) || !Details.contains(QStringLiteral("height")))
62  return;
63 
64  bool bitrate = Details.contains(QStringLiteral("bitrate"));
65  bool framerate = Details.contains(QStringLiteral("framerate"));
66  bool video = bitrate && framerate;
67  bool stills = !bitrate && !framerate;
68 
69  if (!video && !stills)
70  return;
71 
72  m_width = Details.value(QStringLiteral("width")).toInt();
73  m_height = Details.value(QStringLiteral("height")).toInt();
74 
75  // N.B. pitch and slice height may be further constrained in certain encoders and may be further adjusted
76  // most h264 streams will expect 16 pixel aligned video so round up
77  int pitch = (m_width + 15) & ~15;
78  if (pitch != m_width)
79  {
80  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Rounding video width up to %1 from %2").arg(pitch).arg(m_width));
81  m_width = pitch;
82  }
83 
84  // and 8 pixel macroblock height
85  int height = (m_height + 7) & ~7;
86  if (height != m_height)
87  {
88  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Rounding video height up to %1 from %2").arg(height).arg(m_height));
89  m_height = height;
90  }
91 
92  m_stride = m_width;
94 
95  bool forcemin = m_width < VIDEO_WIDTH_MIN || m_height < VIDEO_HEIGHT_MIN;
96  bool forcemax = m_width > VIDEO_WIDTH_MAX || m_height > VIDEO_HEIGHT_MAX;
97  if (forcemin)
98  {
101  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Video too small - forcing output to %1x%2").arg(m_width).arg(m_height));
102  }
103  else if (forcemax)
104  {
107  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Video too large - forcing output to %1x%2").arg(m_width).arg(m_height));
108  }
109 
110  if (video)
111  {
112  m_frameRate = Details.value(QStringLiteral("framerate")).toInt();
113  m_bitrate = Details.value(QStringLiteral("bitrate")).toInt();
114 
116  {
118  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Video framerate too low - forcing to %1").arg(m_frameRate));
119  }
120  else if (m_frameRate > VIDEO_FRAMERATE_MAX)
121  {
123  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Video framerate too high - forcing to %1").arg(m_frameRate));
124  }
125 
127  {
129  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Video bitrate too low - forcing to %1").arg(m_bitrate));
130  }
131  else if (m_bitrate > VIDEO_BITRATE_MAX)
132  {
134  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Video bitrate too high - forcing to %1").arg(m_bitrate));
135  }
136 
139 
140  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Segment length: %1frames %2seconds").arg(m_segmentLength).arg(m_segmentLength / m_frameRate));
141  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("GOP length: %1frames %2seconds").arg(m_gopSize).arg(m_gopSize / m_frameRate));
142  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Camera video : %1x%2@%3fps bitrate %4").arg(m_width).arg(m_height).arg(m_frameRate).arg(m_bitrate));
143  }
144  else
145  {
146  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Camera stills: %1x%2").arg(m_width).arg(m_height));
147  }
148 
149  m_valid = true;
150 }
151 
153 {
154  if (&Other != this)
155  {
156  this->m_valid = Other.m_valid;
157  this->m_width = Other.m_width;
158  this->m_height = Other.m_height;
159  this->m_stride = Other.m_stride;
160  this->m_sliceHeight = Other.m_sliceHeight;
161  this->m_frameRate = Other.m_frameRate;
162  this->m_bitrate = Other.m_bitrate;
163  this->m_timebase = Other.m_timebase;
164  this->m_segmentLength = Other.m_segmentLength;
165  this->m_gopSize = Other.m_gopSize;
166  this->m_videoCodec = Other.m_videoCodec;
167  this->m_contentDir = Other.m_contentDir;
168  }
169  return *this;
170 }
171 
173 {
174  return this->m_valid == Other.m_valid &&
175  this->m_width == Other.m_width &&
176  this->m_height == Other.m_height &&
177  this->m_stride == Other.m_stride &&
178  this->m_sliceHeight == Other.m_sliceHeight &&
179  this->m_frameRate == Other.m_frameRate &&
180  this->m_bitrate == Other.m_bitrate &&
181  this->m_timebase == Other.m_timebase &&
182  this->m_segmentLength == Other.m_segmentLength &&
183  this->m_gopSize == Other.m_gopSize;
184  // ignore codec - it is set by the camera device
185  //this->m_videoCodec == Other.m_videoCodec;
186  //this->m_contentDir == Other.m_contentDir;
187 }
188 
190 {
191  if (!Add.m_valid)
192  return *this;
193  if (!m_valid)
194  return Add;
195  if (Add == *this)
196  return *this;
197 
198  if (!IsCompatible(Add))
199  {
200  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error combining camera parameters - %1x%2 != %3x%4")
201  .arg(m_width).arg(m_height).arg(Add.m_width).arg(Add.m_height));
202  // this should cover most cases
203  if ((m_width > Add.m_width) || (m_height > Add.m_height))
204  {
205  m_width = Add.m_width;
206  m_height = Add.m_height;
207  }
208  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Using smaller image size %1x%2").arg(m_width).arg(m_height));
209  }
210 
211  // add video data
212  if (IsStill() && Add.IsVideo())
213  {
214  m_bitrate = Add.m_bitrate;
215  m_frameRate = Add.m_frameRate;
216  m_gopSize = Add.m_gopSize;
217  m_stride = Add.m_stride;
221  m_timebase = Add.m_timebase;
222  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Added video to camera parameters"));
223  }
224  // add stills
225  else if (IsVideo() && Add.IsStill())
226  {
228  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Added stills to camera parameters"));
229  }
230 
231  return *this;
232 }
233 
235 {
236  return m_valid && (m_frameRate > 1) && (m_bitrate > 0);
237 }
238 
240 {
241  return m_valid && (m_frameRate < 2) && (m_bitrate < 1) && !m_contentDir.isEmpty();
242 }
243 
245 {
246  return m_valid && Other.m_valid && (Other.m_width == m_width) && (Other.m_height == m_height);
247 }
248 
250  : QObject(),
251  m_params(Params),
252  m_muxer(nullptr),
253  m_videoStream(0),
254  m_frameCount(0),
255  m_haveInitSegment(false),
256  m_bufferedPacket(nullptr),
257  m_ringBuffer(nullptr),
258  m_ringBufferLock(QReadWriteLock::Recursive),
259  m_referenceTime(0),
260  m_discardDrift(2),
261  m_shortAverage(VIDEO_DRIFT_SHORT / VIDEO_SEGMENT_TARGET),
262  m_longAverage(VIDEO_DRIFT_LONG / VIDEO_SEGMENT_TARGET),
263  m_stillsRequired(0),
264  m_stillsExpected(0),
265  m_stillsBuffers()
266 {
267 }
268 
270 {
271  if (m_muxer)
272  {
273  m_muxer->Finish();
274  delete m_muxer;
275  }
276 
277  if (m_ringBuffer)
278  delete m_ringBuffer;
279 
280  if (m_bufferedPacket)
281  av_packet_free(&m_bufferedPacket);
282 
284 }
285 
287 {
288  QWriteLocker locker(&m_ringBufferLock);
292  if (!m_muxer)
293  return false;
294 
296  if (!m_muxer->IsValid())
297  return false;
298 
302 
303  m_frameCount = 0;
304  m_bufferedPacket = nullptr;
305  m_haveInitSegment = false;
306  return true;
307 }
308 
309 QByteArray TorcCameraDevice::GetSegment(int Segment)
310 {
311  QReadLocker locker(&m_ringBufferLock);
312  if (m_ringBuffer)
313  return m_ringBuffer->GetSegment(Segment);
314  return QByteArray();
315 }
316 
318 {
319  QReadLocker locker(&m_ringBufferLock);
320  if (m_ringBuffer)
321  return m_ringBuffer->GetInitSegment();
322  return QByteArray();
323 }
324 
331 {
332  if (Count > m_stillsRequired)
333  {
334  bool started = m_stillsExpected > 0;
335  (void)EnableStills(Count);
336  if (!started)
337  StartStill();
338  }
339 }
340 
342 {
343  m_stillsRequired = Count;
344  m_stillsExpected = Count;
345  return true;
346 }
347 
349 {
350  if (m_stillsExpected)
351  {
352  if (m_stillsRequired)
353  {
354  if (!m_params.m_contentDir.isEmpty())
355  {
356  QFile file(m_params.m_contentDir + QDateTime::currentDateTime().toString(QStringLiteral("yyyy_MM_dd_hh_mm_ss_zzz")) + ".jpg");
357  if (file.open(QIODevice::ReadWrite | QIODevice::Truncate))
358  {
359  QPair<quint32, uint8_t*> pair;
360  foreach(pair, m_stillsBuffers)
361  file.write((const char*)pair.second, pair.first);
362  file.close();
363  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Saved snapshot as '%1'").arg(file.fileName()));
364  emit StillReady(file.fileName());
365  }
366  else
367  {
368  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to open %1 for writing").arg(file.fileName()));
369  }
370  }
372  }
374  }
376 }
377 
378 void TorcCameraDevice::SaveStillBuffer(quint32 Length, uint8_t *Data)
379 {
380  if (Length < 1 || !Data)
381  return;
382 
383  m_stillsBuffers.append(QPair<quint32,uint8_t*>(Length, Data));
384 }
385 
387 {
388  QPair<quint32, uint8_t*> buffer;
389  foreach(buffer, m_stillsBuffers)
390  free(buffer.second);
391  m_stillsBuffers.clear();
392 }
393 
395 {
396  quint64 now = QDateTime::currentMSecsSinceEpoch();
397  if (!m_referenceTime)
398  m_referenceTime = now;
399 
400  // discard first few samples while camera settles down
401  if (m_discardDrift)
402  {
403  m_discardDrift--;
404  return;
405  }
406 
407  // reference time points to the end of the first video segment
408  m_ringBufferLock.lockForRead();
409  int last = m_ringBuffer->GetHead();
410  m_ringBufferLock.unlock();
411  if (last < 0)
412  return;
413 
414  // all milliseconds
415  qint64 drift = QDateTime::currentMSecsSinceEpoch() - (m_referenceTime + (last * VIDEO_SEGMENT_TARGET * 1000));
416  double shortaverage = m_shortAverage.AddValue(drift);
417  double longaverage = m_longAverage.AddValue(drift);
418 
419  static const double timedelta = ((VIDEO_DRIFT_LONG - VIDEO_DRIFT_SHORT) / 2) * 1000;
420  double driftdelta = longaverage - shortaverage;
421  double gradient = driftdelta / timedelta;
422  double timetozero = qAbs(driftdelta) < 1 ? qInf() : drift / gradient;
423 
424  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Drift: %1secs 1min %2 5min %3 timetozero %4")
425  .arg((double)drift/1000, 0, 'f', 3).arg(shortaverage/1000, 0, 'f', 3).arg(longaverage/1000, 0, 'f', 3).arg(timetozero/1000, 0, 'f', 3));
426 }
427 
429 
431  : nextTorcCameraFactory(gTorcCameraFactory)
432 {
433  qRegisterMetaType<TorcCameraParams>("TorcCameraParams&");
434  gTorcCameraFactory = this;
435 }
436 
438 {
439  return gTorcCameraFactory;
440 }
441 
443 {
444  return nextTorcCameraFactory;
445 }
446 
448 {
449  TorcCameraDevice* result = nullptr;
450 
452  for ( ; factory; factory = factory->NextFactory())
453  {
454  if (factory->CanHandle(Type, Params))
455  {
456  result = factory->Create(Type, Params);
457  break;
458  }
459  }
460 
461  return result;
462 }
static TorcCameraFactory * GetTorcCameraFactory(void)
Definition: torccamera.cpp:437
#define VIDEO_TIMEBASE
Definition: torccamera.h:28
#define VIDEO_FRAMERATE_MIN
Definition: torccamera.h:20
void ClearStillsBuffers(void)
Definition: torccamera.cpp:386
TorcAverage< double > m_shortAverage
Definition: torccamera.h:114
static TorcCameraDevice * GetCamera(const QString &Type, const TorcCameraParams &Params)
Definition: torccamera.cpp:447
void Finish(void)
Definition: torcmuxer.cpp:490
QString m_videoCodec
Definition: torccamera.h:58
TorcCameraParams(void)
Definition: torccamera.cpp:31
int GetHead(void)
Return the number of the segment at the head of the queue (the newest).
#define VIDEO_WIDTH_MAX
Definition: torccamera.h:17
int AddH264Stream(int Width, int Height, int Profile, int Bitrate)
Definition: torcmuxer.cpp:395
#define VIDEO_H264_PROFILE
Definition: torccamera.h:31
QByteArray GetInitSegment(void)
Return a copy of the MP4 &#39;init&#39; segment.
virtual bool CanHandle(const QString &Type, const TorcCameraParams &Params)=0
TorcCameraParams m_params
Definition: torccamera.h:102
#define VIDEO_HEIGHT_MIN
Definition: torccamera.h:18
QByteArray GetInitSegment(void)
Definition: torccamera.cpp:317
TorcCameraFactory * NextFactory(void) const
Definition: torccamera.cpp:442
bool IsValid(void)
Definition: torcmuxer.cpp:288
void SegmentRemoved(int Segment)
QReadWriteLock m_ringBufferLock
Definition: torccamera.h:111
TorcAverage< double > m_longAverage
Definition: torccamera.h:115
QString m_contentDir
Definition: torccamera.h:59
virtual void TakeStills(uint Count)
Tell the camera to take Count number of still images.
Definition: torccamera.cpp:330
AVPacket * m_bufferedPacket
Definition: torccamera.h:109
bool IsVideo(void) const
Definition: torccamera.cpp:234
#define VIDEO_WIDTH_MIN
Definition: torccamera.h:16
#define VIDEO_HEIGHT_MAX
Definition: torccamera.h:19
virtual void StartStill(void)=0
virtual bool EnableStills(uint Count)
Definition: torccamera.cpp:341
quint64 m_frameCount
Definition: torccamera.h:107
#define VIDEO_SEGMENT_TARGET
Definition: torccamera.h:24
#define VIDEO_DRIFT_SHORT
Definition: torccamera.h:29
bool m_haveInitSegment
Definition: torccamera.h:108
quint64 m_referenceTime
Definition: torccamera.h:112
#define VIDEO_SEGMENT_NUMBER
Definition: torccamera.h:26
double AddValue(T Value)
Definition: torcmaths.h:22
void SegmentReady(int Segment)
virtual ~TorcCameraDevice()
Definition: torccamera.cpp:269
void SegmentRemoved(int Segment)
#define VIDEO_BITRATE_MIN
Definition: torccamera.h:22
virtual TorcCameraDevice * Create(const QString &Type, const TorcCameraParams &Params)=0
bool IsCompatible(const TorcCameraParams &Other) const
Definition: torccamera.cpp:244
static TorcCameraFactory * gTorcCameraFactory
Definition: torccamera.h:140
TorcCameraParams & operator=(const TorcCameraParams &Other)
Definition: torccamera.cpp:152
void StillReady(const QString &File)
QByteArray GetSegment(int Segment)
Definition: torccamera.cpp:309
TorcCameraFactory * nextTorcCameraFactory
Definition: torccamera.h:141
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
virtual bool Setup(void)
Definition: torccamera.cpp:286
bool operator==(const TorcCameraParams &Other) const
Definition: torccamera.cpp:172
void SaveStill(void)
Definition: torccamera.cpp:348
void InitSegmentReady(void)
#define VIDEO_SEGMENT_MAX
Definition: torccamera.h:27
QList< QPair< quint32, uint8_t * > > m_stillsBuffers
Definition: torccamera.h:120
TorcCameraParams Combine(const TorcCameraParams &Add)
Definition: torccamera.cpp:189
TorcCameraDevice(const TorcCameraParams &Params)
Definition: torccamera.cpp:249
#define VIDEO_DRIFT_LONG
Definition: torccamera.h:30
#define VIDEO_BITRATE_MAX
Definition: torccamera.h:23
bool IsStill(void) const
Definition: torccamera.cpp:239
TorcMuxer * m_muxer
Definition: torccamera.h:105
void SegmentReady(int Segment)
#define VIDEO_GOPDURA_TARGET
Definition: torccamera.h:25
TorcSegmentedRingBuffer * m_ringBuffer
Definition: torccamera.h:110
void SaveStillBuffer(quint32 Length, uint8_t *Data)
Definition: torccamera.cpp:378
QByteArray GetSegment(int SegmentRef)
Return a copy of the segment identified by SegmentRef.
#define VIDEO_FRAMERATE_MAX
Definition: torccamera.h:21
void TrackDrift(void)
Definition: torccamera.cpp:394