1 /*
2     Copyright © 2015-2019 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 extern "C" {
21 #include <libavcodec/avcodec.h>
22 #include <libavutil/imgutils.h>
23 }
24 
25 #include "corevideosource.h"
26 #include "videoframe.h"
27 
28 /**
29  * @class CoreVideoSource
30  * @brief A VideoSource that emits frames received by Core.
31  */
32 
33 /**
34  * @var std::atomic_int subscribers
35  * @brief Number of suscribers
36  *
37  * @var std::atomic_bool deleteOnClose
38  * @brief If true, self-delete after the last suscriber is gone
39  */
40 
41 /**
42  * @brief CoreVideoSource constructor.
43  * @note Only CoreAV should create a CoreVideoSource since
44  * only CoreAV can push images to it.
45  */
CoreVideoSource()46 CoreVideoSource::CoreVideoSource()
47     : subscribers{0}
48     , deleteOnClose{false}
49     , stopped{false}
50 {
51 }
52 
53 /**
54  * @brief Makes a copy of the vpx_image_t and emits it as a new VideoFrame.
55  * @param vpxframe Frame to copy.
56  */
pushFrame(const vpx_image_t * vpxframe)57 void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
58 {
59     if (stopped)
60         return;
61 
62     QMutexLocker locker(&biglock);
63 
64     std::shared_ptr<VideoFrame> vframe;
65     int width = vpxframe->d_w;
66     int height = vpxframe->d_h;
67 
68     if (subscribers <= 0)
69         return;
70 
71     AVFrame* avframe = av_frame_alloc();
72     if (!avframe)
73         return;
74 
75     avframe->width = width;
76     avframe->height = height;
77     avframe->format = AV_PIX_FMT_YUV420P;
78 
79     int bufSize =
80         av_image_alloc(avframe->data, avframe->linesize, width, height,
81                        static_cast<AVPixelFormat>(AV_PIX_FMT_YUV420P), VideoFrame::dataAlignment);
82 
83     if (bufSize < 0) {
84         av_frame_free(&avframe);
85         return;
86     }
87 
88     for (int i = 0; i < 3; ++i) {
89         int dstStride = avframe->linesize[i];
90         int srcStride = vpxframe->stride[i];
91         int minStride = std::min(dstStride, srcStride);
92         int size = (i == 0) ? height : height / 2;
93 
94         for (int j = 0; j < size; ++j) {
95             uint8_t* dst = avframe->data[i] + dstStride * j;
96             uint8_t* src = vpxframe->planes[i] + srcStride * j;
97             memcpy(dst, src, minStride);
98         }
99     }
100 
101     vframe = std::make_shared<VideoFrame>(id, avframe, true);
102     emit frameAvailable(vframe);
103 }
104 
subscribe()105 void CoreVideoSource::subscribe()
106 {
107     QMutexLocker locker(&biglock);
108     ++subscribers;
109 }
110 
unsubscribe()111 void CoreVideoSource::unsubscribe()
112 {
113     biglock.lock();
114     if (--subscribers == 0) {
115         if (deleteOnClose) {
116             biglock.unlock();
117             // DANGEROUS: No member access after this point, that's why we manually unlock
118             delete this;
119             return;
120         }
121     }
122     biglock.unlock();
123 }
124 
125 /**
126  * @brief Setup delete on close
127  * @param If true, self-delete after the last suscriber is gone
128  */
setDeleteOnClose(bool newstate)129 void CoreVideoSource::setDeleteOnClose(bool newstate)
130 {
131     QMutexLocker locker(&biglock);
132     deleteOnClose = newstate;
133 }
134 
135 /**
136  * @brief Stopping the source.
137  * @see The callers in CoreAV for the rationale
138  *
139  * Stopping the source will block any pushFrame calls from doing anything
140  */
stopSource()141 void CoreVideoSource::stopSource()
142 {
143     QMutexLocker locker(&biglock);
144     stopped = true;
145     emit sourceStopped();
146 }
147 
restartSource()148 void CoreVideoSource::restartSource()
149 {
150     QMutexLocker locker(&biglock);
151     stopped = false;
152 }
153