forked from wang-bin/QtAV
-
Notifications
You must be signed in to change notification settings - Fork 0
/
VideoThread.cpp
233 lines (218 loc) · 8.77 KB
/
VideoThread.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/******************************************************************************
QtAV: Media play library based on Qt and FFmpeg
Copyright (C) 2012-2013 Wang Bin <[email protected]>
* This file is part of QtAV
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
******************************************************************************/
#include <QtAV/VideoThread.h>
#include <private/AVThread_p.h>
#include <QtAV/Packet.h>
#include <QtAV/AVClock.h>
#include <QtAV/VideoCapture.h>
#include <QtAV/VideoDecoder.h>
#include <QtAV/VideoRenderer.h>
#include <QtAV/ImageConverter.h>
#include <QtGui/QImage>
#include <QtAV/Statistics.h>
#include <QtAV/Filter.h>
#include <QtAV/FilterContext.h>
#include <QtAV/OutputSet.h>
namespace QtAV {
class VideoThreadPrivate : public AVThreadPrivate
{
public:
VideoThreadPrivate():
conv(0)
, capture(0)
{}
ImageConverter *conv;
double pts; //current decoded pts. for capture. TODO: remove
//QImage image; //use QByteArray? Then must allocate a picture in ImageConverter, see VideoDecoder
VideoCapture *capture;
};
VideoThread::VideoThread(QObject *parent) :
AVThread(*new VideoThreadPrivate(), parent)
{
}
ImageConverter* VideoThread::setImageConverter(ImageConverter *converter)
{
DPTR_D(VideoThread);
QtAV::ImageConverter* old = d.conv;
d.conv = converter;
return old;
}
ImageConverter* VideoThread::imageConverter() const
{
return d_func().conv;
}
double VideoThread::currentPts() const
{
return d_func().pts;
}
//it is called in main thread usually, but is being used in video thread,
VideoCapture* VideoThread::setVideoCapture(VideoCapture *cap)
{
qDebug("setCapture %p", cap);
DPTR_D(VideoThread);
QMutexLocker locker(&d.mutex);
VideoCapture *old = d.capture;
d.capture = cap;
return old;
}
//TODO: if output is null or dummy, the use duration to wait
void VideoThread::run()
{
DPTR_D(VideoThread);
//TODO: no d.writer is ok, just a audio player
#if V1_2
if (!d.dec || !d.dec->isAvailable() || !d.writer)// || !d.conv)
return;
#else
if (!d.dec || !d.dec->isAvailable() || !d.outputSet)// || !d.conv)
return;
#endif //V1_2
resetState();
Q_ASSERT(d.clock != 0);
VideoDecoder *dec = static_cast<VideoDecoder*>(d.dec);
if (dec) {
//used to initialize the decoder's frame size
dec->resizeVideoFrame(0, 0);
}
bool need_update_vo_parameters = true;
QSize dec_size_last;
while (!d.stop) {
processNextTask();
//TODO: why put it at the end of loop then playNextFrame() not work?
//processNextTask tryPause(timeout) and and continue outter loop
if (tryPause()) { //DO NOT continue, or playNextFrame() will fail
if (d.stop)
break; //the queue is empty and may block. should setBlocking(false) wake up cond empty?
} else {
if (isPaused())
continue; //timeout. process pending tasks
}
QMutexLocker locker(&d.mutex);
Q_UNUSED(locker);
if (d.packets.isEmpty() && !d.stop) {
d.stop = d.demux_end;
if (d.stop) {
qDebug("video queue empty and demux end. break video thread");
break;
}
}
Packet pkt = d.packets.take(); //wait to dequeue
//Compare to the clock
if (!pkt.isValid()) {
qDebug("Invalid packet! flush video codec context!!!!!!!!!!");
dec->flush();
continue;
}
d.delay = pkt.pts - d.clock->value();
/*
*after seeking forward, a packet may be the old, v packet may be
*the new packet, then the d.delay is very large, omit it.
*TODO: 1. how to choose the value
* 2. use last delay when seeking
*/
if (qAbs(d.delay) < 2.718) {
if (d.delay > kSyncThreshold) { //Slow down
//d.delay_cond.wait(&d.mutex, d.delay*1000); //replay may fail. why?
//qDebug("~~~~~wating for %f msecs", d.delay*1000);
usleep(d.delay * 1000000);
} else if (d.delay < -kSyncThreshold) { //Speed up. drop frame?
//continue;
}
} else { //when to drop off?
qDebug("delay %f/%f", d.delay, d.clock->value());
if (d.delay > 0) {
msleep(64);
} else {
//audio packet not cleaned up?
continue;
}
}
d.clock->updateVideoPts(pkt.pts); //here?
#if V1_2
//DO NOT decode and convert if vo is not available or null!
//NOTE: vo may changes dymanically. we need check it every time we use it. decoder and other components should do this too
VideoRenderer* vo = static_cast<VideoRenderer*>(d.writer);
bool vo_ok = vo && vo->isAvailable();
if (vo_ok) {
//use the last size first then update the last size so that decoder(converter) can update output size
if (vo->lastWidth() > 0 && vo->lastHeight() > 0 && !vo->scaleInRenderer())
dec->resizeVideoFrame(vo->lastSize());
else
vo->setInSize(dec->width(), dec->height()); //setLastSize(). optimize: set only when changed
}
#else
if (need_update_vo_parameters || !d.update_outputs.isEmpty()) {
//lock is important when iterating
d.outputSet->lock();
if (need_update_vo_parameters) {
d.update_outputs = d.outputSet->outputs();
need_update_vo_parameters = false;
}
foreach(AVOutput *out, d.update_outputs) {
VideoRenderer *vo = static_cast<VideoRenderer*>(out);
vo->setInSize(dec->width(), dec->height()); //setLastSize(). optimize: set only when changed
}
d.outputSet->unlock();
d.update_outputs.clear();
}
need_update_vo_parameters = dec_size_last.width() != dec->width() || dec_size_last.height() != dec->height();
dec_size_last = QSize(dec->width(), dec->height());
#endif //V1_2
//still decode, we may need capture. TODO: decode only if existing a capture request if no vo
if (dec->decode(pkt.data)) {
d.pts = pkt.pts;
if (d.capture) {
d.capture->setRawImage(dec->data(), dec->width(), dec->height());
}
//TODO: Add filters here. Capture is also a filter
/*if (d.image.width() != d.renderer_width || d.image.height() != d.renderer_height)
d.image = QImage(d.renderer_width, d.renderer_height, QImage::Format_RGB32);
d.conv->setInSize(d.renderer_width, d.renderer_height);
if (!d.conv->convert(d.decoded_data.constData(), d.image.bits())) {
}*/
QByteArray data = dec->data();
if (d.statistics) {
d.statistics->video.current_time = QTime(0, 0, 0).addMSecs(int(pkt.pts * 1000.0)); //TODO: is it expensive?
if (!d.filters.isEmpty()) {
foreach (Filter *filter, d.filters) {
filter->process(d.filter_context, d.statistics, &data);
}
}
}
#if V1_2
if (vo_ok) {
vo->writeData(data);
}
#else
//while can pause, processNextTask, not call outset.puase which is deperecated
while (d.outputSet->canPauseThread()) {
d.outputSet->pauseThread(100);
//tryPause(100);
processNextTask();
}
d.outputSet->sendData(data);
#endif //V1_2
}
#if V1_2
//use the last size first then update the last size so that decoder(converter) can update output size
if (vo_ok && !vo->scaleInRenderer())
vo->setInSize(vo->rendererSize());//out size?
#endif //V1_2
}
qDebug("Video thread stops running...");
}
} //namespace QtAV