qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
videoframe.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2014-2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "videoframe.h"
21 
22 extern "C" {
23 #include <libavutil/imgutils.h>
24 #include <libswscale/swscale.h>
25 }
26 
75 // Initialize static fields
77 
78 std::unordered_map<VideoFrame::IDType, QMutex> VideoFrame::mutexMap{};
79 std::unordered_map<VideoFrame::IDType, std::unordered_map<VideoFrame::IDType, std::weak_ptr<VideoFrame>>>
81 
82 QReadWriteLock VideoFrame::refsLock{};
83 
93 VideoFrame::VideoFrame(IDType sourceID, AVFrame* sourceFrame, QRect dimensions, int pixFmt,
94  bool freeSourceFrame)
95  : frameID(frameIDs++)
96  , sourceID(sourceID)
97  , sourceDimensions(dimensions)
98  , sourceFrameKey(getFrameKey(dimensions.size(), pixFmt, sourceFrame->linesize[0]))
99  , freeSourceFrame(freeSourceFrame)
100 {
101 
102  // We override the pixel format in the case a deprecated one is used
103  switch (pixFmt) {
104  case AV_PIX_FMT_YUVJ420P: {
105  sourcePixelFormat = AV_PIX_FMT_YUV420P;
106  sourceFrame->color_range = AVCOL_RANGE_MPEG;
107  break;
108  }
109 
110  case AV_PIX_FMT_YUVJ411P: {
111  sourcePixelFormat = AV_PIX_FMT_YUV411P;
112  sourceFrame->color_range = AVCOL_RANGE_MPEG;
113  break;
114  }
115 
116  case AV_PIX_FMT_YUVJ422P: {
117  sourcePixelFormat = AV_PIX_FMT_YUV422P;
118  sourceFrame->color_range = AVCOL_RANGE_MPEG;
119  break;
120  }
121 
122  case AV_PIX_FMT_YUVJ444P: {
123  sourcePixelFormat = AV_PIX_FMT_YUV444P;
124  sourceFrame->color_range = AVCOL_RANGE_MPEG;
125  break;
126  }
127 
128  case AV_PIX_FMT_YUVJ440P: {
129  sourcePixelFormat = AV_PIX_FMT_YUV440P;
130  sourceFrame->color_range = AVCOL_RANGE_MPEG;
131  break;
132  }
133 
134  default: {
135  sourcePixelFormat = pixFmt;
136  sourceFrame->color_range = AVCOL_RANGE_UNSPECIFIED;
137  }
138  }
139 
140  frameBuffer[sourceFrameKey] = sourceFrame;
141 }
142 
143 VideoFrame::VideoFrame(IDType sourceID, AVFrame* sourceFrame, bool freeSourceFrame)
144  : VideoFrame(sourceID, sourceFrame, QRect{0, 0, sourceFrame->width, sourceFrame->height},
145  sourceFrame->format, freeSourceFrame)
146 {
147 }
148 
153 {
154  // Release frame
155  frameLock.lockForWrite();
156 
158 
159  frameLock.unlock();
160 
161  // Delete tracked reference
162  refsLock.lockForRead();
163 
164  if (refsMap.count(sourceID) > 0) {
165  QMutex& sourceMutex = mutexMap[sourceID];
166 
167  sourceMutex.lock();
168  refsMap[sourceID].erase(frameID);
169  sourceMutex.unlock();
170  }
171 
172  refsLock.unlock();
173 }
174 
184 {
185  frameLock.lockForRead();
186  bool retValue = frameBuffer.size() > 0;
187  frameLock.unlock();
188 
189  return retValue;
190 }
191 
200 std::shared_ptr<VideoFrame> VideoFrame::trackFrame()
201 {
202  // Add frame to tracked reference list
203  refsLock.lockForRead();
204 
205  if (refsMap.count(sourceID) == 0) {
206  // We need to add a new source to our reference map, obtain write lock
207  refsLock.unlock();
208  refsLock.lockForWrite();
209  }
210 
211  QMutex& sourceMutex = mutexMap[sourceID];
212 
213  sourceMutex.lock();
214 
215  std::shared_ptr<VideoFrame> ret{this};
216 
217  refsMap[sourceID][frameID] = ret;
218 
219  sourceMutex.unlock();
220  refsLock.unlock();
221 
222  return ret;
223 }
224 
236 void VideoFrame::untrackFrames(const VideoFrame::IDType& sourceID, bool releaseFrames)
237 {
238  refsLock.lockForWrite();
239 
240  if (refsMap.count(sourceID) == 0) {
241  // No tracking reference exists for source, simply return
242  refsLock.unlock();
243 
244  return;
245  }
246 
247  if (releaseFrames) {
248  QMutex& sourceMutex = mutexMap[sourceID];
249 
250  sourceMutex.lock();
251 
252  for (auto& frameIterator : refsMap[sourceID]) {
253  std::shared_ptr<VideoFrame> frame = frameIterator.second.lock();
254 
255  if (frame) {
256  frame->releaseFrame();
257  }
258  }
259 
260  sourceMutex.unlock();
261  }
262 
263  refsMap[sourceID].clear();
264 
265  mutexMap.erase(sourceID);
266  refsMap.erase(sourceID);
267 
268  refsLock.unlock();
269 }
270 
275 {
276  frameLock.lockForWrite();
277 
279 
280  frameLock.unlock();
281 }
282 
296 const AVFrame* VideoFrame::getAVFrame(QSize frameSize, const int pixelFormat, const bool requireAligned)
297 {
298  if (!frameSize.isValid()) {
299  frameSize = sourceDimensions.size();
300  }
301 
302  // Since we are retrieving the AVFrame* directly, we merely need to pass the arguement through
303  const std::function<AVFrame*(AVFrame * const)> converter = [](AVFrame* const frame) {
304  return frame;
305  };
306 
307  // We need an explicit null pointer holding object to pass to toGenericObject()
308  AVFrame* nullPointer = nullptr;
309 
310  // Returns std::nullptr case of invalid generation
311  return toGenericObject(frameSize, pixelFormat, requireAligned, converter, nullPointer);
312 }
313 
325 QImage VideoFrame::toQImage(QSize frameSize)
326 {
327  if (!frameSize.isValid()) {
328  frameSize = sourceDimensions.size();
329  }
330 
331  // Converter function (constructs QImage out of AVFrame*)
332  const std::function<QImage(AVFrame * const)> converter = [&](AVFrame* const frame) {
333  return QImage{*(frame->data), frameSize.width(), frameSize.height(), *(frame->linesize),
334  QImage::Format_RGB888};
335  };
336 
337  // Returns an empty constructed QImage in case of invalid generation
338  return toGenericObject(frameSize, AV_PIX_FMT_RGB24, false, converter, QImage{});
339 }
340 
353 {
354  if (!frameSize.isValid()) {
355  frameSize = sourceDimensions.size();
356  }
357 
358  // Converter function (constructs ToxAVFrame out of AVFrame*)
359  const std::function<ToxYUVFrame(AVFrame * const)> converter = [&](AVFrame* const frame) {
360  ToxYUVFrame ret{static_cast<std::uint16_t>(frameSize.width()),
361  static_cast<std::uint16_t>(frameSize.height()), frame->data[0],
362  frame->data[1], frame->data[2]};
363 
364  return ret;
365  };
366 
367  return toGenericObject(frameSize, AV_PIX_FMT_YUV420P, true, converter,
368  ToxYUVFrame{0, 0, nullptr, nullptr, nullptr});
369 }
370 
379 {
380  return frameID;
381 }
382 
389 {
390  return sourceID;
391 }
392 
399 {
400  return sourceDimensions;
401 }
402 
409 {
410  return sourcePixelFormat;
411 }
412 
413 
422 VideoFrame::FrameBufferKey::FrameBufferKey(const int width, const int height, const int pixFmt,
423  const bool lineAligned)
424  : frameWidth(width)
425  , frameHeight(height)
426  , pixelFormat(pixFmt)
427  , linesizeAligned(lineAligned)
428 {
429 }
430 
438 {
439  return pixelFormat == other.pixelFormat && frameWidth == other.frameWidth
440  && frameHeight == other.frameHeight && linesizeAligned == other.linesizeAligned;
441 }
442 
450 {
451  return !operator==(other);
452 }
453 
463 {
464  std::hash<int> intHasher;
465  std::hash<bool> boolHasher;
466 
467  // Use java-style hash function to combine fields
468  // See: https://en.wikipedia.org/wiki/Java_hashCode%28%29#hashCode.28.29_in_general
469 
470  size_t ret = 47;
471 
472  ret = 37 * ret + intHasher(key.frameWidth);
473  ret = 37 * ret + intHasher(key.frameHeight);
474  ret = 37 * ret + intHasher(key.pixelFormat);
475  ret = 37 * ret + boolHasher(key.linesizeAligned);
476 
477  return ret;
478 }
479 
488 VideoFrame::FrameBufferKey VideoFrame::getFrameKey(const QSize& frameSize, const int pixFmt,
489  const int linesize)
490 {
491  return getFrameKey(frameSize, pixFmt, frameSize.width() == linesize);
492 }
493 
502 VideoFrame::FrameBufferKey VideoFrame::getFrameKey(const QSize& frameSize, const int pixFmt,
503  const bool frameAligned)
504 {
505  return {frameSize.width(), frameSize.height(), pixFmt, frameAligned};
506 }
507 
523 AVFrame* VideoFrame::retrieveAVFrame(const QSize& dimensions, const int pixelFormat,
524  const bool requireAligned)
525 {
526  if (!requireAligned) {
527  /*
528  * We attempt to obtain a unaligned frame first because an unaligned linesize corresponds
529  * to a data aligned frame.
530  */
531  FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, false);
532 
533  if (frameBuffer.count(frameKey) > 0) {
534  return frameBuffer[frameKey];
535  }
536  }
537 
538  FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, true);
539 
540  if (frameBuffer.count(frameKey) > 0) {
541  return frameBuffer[frameKey];
542  } else {
543  return nullptr;
544  }
545 }
546 
557 AVFrame* VideoFrame::generateAVFrame(const QSize& dimensions, const int pixelFormat,
558  const bool requireAligned)
559 {
560  AVFrame* ret = av_frame_alloc();
561 
562  if (!ret) {
563  return nullptr;
564  }
565 
566  // Populate AVFrame fields
567  ret->width = dimensions.width();
568  ret->height = dimensions.height();
569  ret->format = pixelFormat;
570 
571  /*
572  * We generate a frame under data alignment only if the dimensions allow us to be frame aligned
573  * or if the caller doesn't require frame alignment
574  */
575 
576  int bufSize;
577 
578  const bool alreadyAligned = dimensions.width() % dataAlignment == 0 && dimensions.height() % dataAlignment == 0;
579 
580  if (!requireAligned || alreadyAligned) {
581  bufSize = av_image_alloc(ret->data, ret->linesize, dimensions.width(), dimensions.height(),
582  static_cast<AVPixelFormat>(pixelFormat), dataAlignment);
583  } else {
584  bufSize = av_image_alloc(ret->data, ret->linesize, dimensions.width(), dimensions.height(),
585  static_cast<AVPixelFormat>(pixelFormat), 1);
586  }
587 
588  if (bufSize < 0) {
589  av_frame_free(&ret);
590  return nullptr;
591  }
592 
593  // Bilinear is better for shrinking, bicubic better for upscaling
594  int resizeAlgo = sourceDimensions.width() > dimensions.width() ? SWS_BILINEAR : SWS_BICUBIC;
595 
596  SwsContext* swsCtx =
597  sws_getContext(sourceDimensions.width(), sourceDimensions.height(),
598  static_cast<AVPixelFormat>(sourcePixelFormat), dimensions.width(),
599  dimensions.height(), static_cast<AVPixelFormat>(pixelFormat), resizeAlgo,
600  nullptr, nullptr, nullptr);
601 
602  if (!swsCtx) {
603  av_freep(&ret->data[0]);
604 #if LIBAVCODEC_VERSION_INT < 3747941
605  av_frame_unref(ret);
606 #endif
607  av_frame_free(&ret);
608  return nullptr;
609  }
610 
611  AVFrame* source = frameBuffer[sourceFrameKey];
612 
613  sws_scale(swsCtx, source->data, source->linesize, 0, sourceDimensions.height(), ret->data,
614  ret->linesize);
615  sws_freeContext(swsCtx);
616 
617  return ret;
618 }
619 
642 AVFrame* VideoFrame::storeAVFrame(AVFrame* frame, const QSize& dimensions, const int pixelFormat)
643 {
644  FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, frame->linesize[0]);
645 
646  // We check the prescence of the frame in case of double-computation
647  if (frameBuffer.count(frameKey) > 0) {
648  AVFrame* old_ret = frameBuffer[frameKey];
649 
650  // Free new frame
651  av_freep(&frame->data[0]);
652 #if LIBAVCODEC_VERSION_INT < 3747941
653  av_frame_unref(frame);
654 #endif
655  av_frame_free(&frame);
656 
657  return old_ret;
658  } else {
659  frameBuffer[frameKey] = frame;
660 
661  return frame;
662  }
663 }
664 
671 {
672  // An empty framebuffer represents a frame that's already been freed
673  if (frameBuffer.empty()) {
674  return;
675  }
676 
677  for (const auto& frameIterator : frameBuffer) {
678  AVFrame* frame = frameIterator.second;
679 
680  // Treat source frame and derived frames separately
681  if (sourceFrameKey == frameIterator.first) {
682  if (freeSourceFrame) {
683  av_freep(&frame->data[0]);
684  }
685 #if LIBAVCODEC_VERSION_INT < 3747941
686  av_frame_unref(frame);
687 #endif
688  av_frame_free(&frame);
689  } else {
690  av_freep(&frame->data[0]);
691 #if LIBAVCODEC_VERSION_INT < 3747941
692  av_frame_unref(frame);
693 #endif
694  av_frame_free(&frame);
695  }
696  }
697 
698  frameBuffer.clear();
699 }
700 
721 template <typename T>
722 T VideoFrame::toGenericObject(const QSize& dimensions, const int pixelFormat, const bool requireAligned,
723  const std::function<T(AVFrame* const)>& objectConstructor,
724  const T& nullObject)
725 {
726  frameLock.lockForRead();
727 
728  // We return nullObject if the VideoFrame is no longer valid
729  if (frameBuffer.size() == 0) {
730  frameLock.unlock();
731  return nullObject;
732  }
733 
734  AVFrame* frame = retrieveAVFrame(dimensions, static_cast<int>(pixelFormat), requireAligned);
735 
736  if (frame) {
737  T ret = objectConstructor(frame);
738 
739  frameLock.unlock();
740  return ret;
741  }
742 
743  // VideoFrame does not contain an AVFrame to spec, generate one here
744  frame = generateAVFrame(dimensions, static_cast<int>(pixelFormat), requireAligned);
745 
746  /*
747  * We need to "upgrade" the lock to a write lock so we can update our frameBuffer map.
748  *
749  * It doesn't matter if another thread obtains the write lock before we finish since it is
750  * likely writing to somewhere else. Worst-case scenario, we merely perform the generation
751  * process twice, and discard the old result.
752  */
753  frameLock.unlock();
754  frameLock.lockForWrite();
755 
756  frame = storeAVFrame(frame, dimensions, static_cast<int>(pixelFormat));
757 
758  T ret = objectConstructor(frame);
759 
760  frameLock.unlock();
761  return ret;
762 }
763 
764 // Explicitly specialize VideoFrame::toGenericObject() function
765 template QImage VideoFrame::toGenericObject<QImage>(
766  const QSize& dimensions, const int pixelFormat, const bool requireAligned,
767  const std::function<QImage(AVFrame* const)> &objectConstructor, const QImage& nullObject);
768 template ToxYUVFrame VideoFrame::toGenericObject<ToxYUVFrame>(
769  const QSize& dimensions, const int pixelFormat, const bool requireAligned,
770  const std::function<ToxYUVFrame(AVFrame* const)> &objectConstructor, const ToxYUVFrame& nullObject);
771 
780 {
781  return width > 0 && height > 0;
782 }
783 
787 ToxYUVFrame::operator bool() const
788 {
789  return isValid();
790 }
VideoFrame::mutexMap
static std::unordered_map< IDType, QMutex > mutexMap
Definition: videoframe.h:158
VideoFrame::refsLock
static QReadWriteLock refsLock
Definition: videoframe.h:163
VideoFrame::refsMap
static std::unordered_map< IDType, std::unordered_map< IDType, std::weak_ptr< VideoFrame > > > refsMap
Definition: videoframe.h:159
VideoFrame::FrameBufferKey::frameHeight
const int frameHeight
Definition: videoframe.h:120
VideoFrame::~VideoFrame
~VideoFrame()
Destructor for VideoFrame.
Definition: videoframe.cpp:152
VideoFrame::FrameBufferKey
Definition: videoframe.h:97
VideoFrame::retrieveAVFrame
AVFrame * retrieveAVFrame(const QSize &dimensions, const int pixelFormat, const bool requireAligned)
Retrieves an AVFrame derived from the source based on the given parameters without obtaining a lock.
Definition: videoframe.cpp:523
VideoFrame::toToxYUVFrame
ToxYUVFrame toToxYUVFrame(QSize frameSize={})
Converts this VideoFrame to a ToxAVFrame that shares this VideoFrame's buffer.
Definition: videoframe.cpp:352
VideoFrame::IDType
std::uint_fast64_t IDType
Definition: videoframe.h:59
ToxYUVFrame::isValid
bool isValid() const
Returns whether the given ToxYUVFrame represents a valid frame or not.
Definition: videoframe.cpp:779
VideoFrame::sourceFrameKey
const FrameBufferKey sourceFrameKey
Definition: videoframe.h:152
VideoFrame::freeSourceFrame
const bool freeSourceFrame
Definition: videoframe.h:153
VideoFrame::getSourcePixelFormat
int getSourcePixelFormat() const
Retrieves a copy of the source VideoFormat's pixel format.
Definition: videoframe.cpp:408
VideoFrame::getSourceDimensions
QRect getSourceDimensions() const
Retrieves a copy of the source VideoFrame's dimensions.
Definition: videoframe.cpp:398
VideoFrame::FrameBufferKey::operator==
bool operator==(const FrameBufferKey &other) const
Comparison operator for FrameBufferKey.
Definition: videoframe.cpp:437
VideoFrame::getFrameID
IDType getFrameID() const
Returns the ID for the given frame.
Definition: videoframe.cpp:378
VideoFrame::frameIDs
static AtomicIDType frameIDs
Definition: videoframe.h:156
VideoFrame::sourceID
const IDType sourceID
Definition: videoframe.h:143
VideoFrame::VideoFrame
VideoFrame(IDType sourceID, AVFrame *sourceFrame, QRect dimensions, int pixFmt, bool freeSourceFrame=false)
Constructs a new instance of a VideoFrame, sourced by a given AVFrame pointer.
Definition: videoframe.cpp:93
videoframe.h
VideoFrame::trackFrame
std::shared_ptr< VideoFrame > trackFrame()
Causes the VideoFrame class to maintain an internal reference for the frame.
Definition: videoframe.cpp:200
VideoFrame::getFrameKey
static FrameBufferKey getFrameKey(const QSize &frameSize, const int pixFmt, const int linesize)
Generates a key object based on given parameters.
Definition: videoframe.cpp:488
VideoFrame::deleteFrameBuffer
void deleteFrameBuffer()
Releases all frames within the frame buffer.
Definition: videoframe.cpp:670
VideoFrame::generateAVFrame
AVFrame * generateAVFrame(const QSize &dimensions, const int pixelFormat, const bool requireAligned)
Generates an AVFrame based on the given specifications.
Definition: videoframe.cpp:557
VideoFrame::frameBuffer
std::unordered_map< FrameBufferKey, AVFrame *, std::function< decltype(FrameBufferKey::hash)> > frameBuffer
Definition: videoframe.h:147
VideoFrame::isValid
bool isValid()
Returns the validity of this VideoFrame.
Definition: videoframe.cpp:183
VideoFrame::releaseFrame
void releaseFrame()
Releases all frames managed by this VideoFrame and invalidates it.
Definition: videoframe.cpp:274
VideoFrame::FrameBufferKey::hash
static size_t hash(const FrameBufferKey &key)
Hash function for FrameBufferKey.
Definition: videoframe.cpp:462
VideoFrame
An ownernship and management class for AVFrames.
Definition: videoframe.h:55
VideoFrame::FrameBufferKey::linesizeAligned
const bool linesizeAligned
Definition: videoframe.h:122
VideoFrame::sourceDimensions
const QRect sourceDimensions
Definition: videoframe.h:150
VideoFrame::FrameBufferKey::FrameBufferKey
FrameBufferKey(const int width, const int height, const int pixFmt, const bool lineAligned)
Constructs a new FrameBufferKey with the given attributes.
Definition: videoframe.cpp:422
VideoFrame::sourcePixelFormat
int sourcePixelFormat
Definition: videoframe.h:151
VideoFrame::getAVFrame
const AVFrame * getAVFrame(QSize frameSize, const int pixelFormat, const bool requireAligned)
Retrieves an AVFrame derived from the source based on the given parameters.
Definition: videoframe.cpp:296
VideoFrame::toQImage
QImage toQImage(QSize frameSize={})
Converts this VideoFrame to a QImage that shares this VideoFrame's buffer.
Definition: videoframe.cpp:325
VideoFrame::toGenericObject
T toGenericObject(const QSize &dimensions, const int pixelFormat, const bool requireAligned, const std::function< T(AVFrame *const)> &objectConstructor, const T &nullObject)
Converts this VideoFrame to a generic type T based on the given parameters and supplied converter fun...
Definition: videoframe.cpp:722
VideoFrame::dataAlignment
static constexpr int dataAlignment
Data alignment parameter used to populate AVFrame buffers.
Definition: videoframe.h:94
ToxYUVFrame
A simple structure to represent a ToxYUV video frame (corresponds to a frame encoded under format: AV...
Definition: videoframe.h:41
VideoFrame::FrameBufferKey::pixelFormat
const int pixelFormat
Definition: videoframe.h:121
VideoFrame::AtomicIDType
std::atomic_uint_fast64_t AtomicIDType
Definition: videoframe.h:60
VideoFrame::getSourceID
IDType getSourceID() const
Returns the ID for the VideoSource which created this frame.
Definition: videoframe.cpp:388
VideoFrame::untrackFrames
static void untrackFrames(const IDType &sourceID, bool releaseFrames=false)
Untracks all the frames for the given VideoSource, releasing them if specified.
Definition: videoframe.cpp:236
VideoFrame::frameID
const IDType frameID
Definition: videoframe.h:142
VideoFrame::frameLock
QReadWriteLock frameLock
Definition: videoframe.h:162
VideoFrame::FrameBufferKey::frameWidth
const int frameWidth
Definition: videoframe.h:119
VideoFrame::storeAVFrame
AVFrame * storeAVFrame(AVFrame *frame, const QSize &dimensions, const int pixelFormat)
Stores a given AVFrame within the frameBuffer map.
Definition: videoframe.cpp:642
VideoFrame::FrameBufferKey::operator!=
bool operator!=(const FrameBufferKey &other) const
Not equal to operator for FrameBufferKey.
Definition: videoframe.cpp:449