453 lines
11 KiB
C++
453 lines
11 KiB
C++
/******************************************************************************
|
|
QtAV: Multimedia framework based on Qt and FFmpeg
|
|
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
|
|
|
* 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 "AVThread.h"
|
|
#include <limits>
|
|
#include "AVThread_p.h"
|
|
#include "AVClock.h"
|
|
#include "AVDecoder.h"
|
|
#include "AVOutput.h"
|
|
#include "Filter.h"
|
|
#include "OutputSet.h"
|
|
#include "Logger.h"
|
|
|
|
namespace FAV {
|
|
|
|
QVariantHash AVThreadPrivate::dec_opt_framedrop;
|
|
QVariantHash AVThreadPrivate::dec_opt_normal;
|
|
|
|
AVThreadPrivate::~AVThreadPrivate() {
|
|
|
|
stop = true;
|
|
if (!paused) {
|
|
qDebug("~AVThreadPrivate wake up paused thread");
|
|
paused = false;
|
|
next_pause = false;
|
|
cond.wakeAll();
|
|
}
|
|
packets.setBlocking(true); //???
|
|
packets.clear();
|
|
QList<Filter*>::iterator it = filters.begin();
|
|
while (it != filters.end()) {
|
|
if ((*it)->isOwnedByTarget() && !(*it)->parent())
|
|
delete *it;
|
|
++it;
|
|
}
|
|
filters.clear();
|
|
}
|
|
|
|
AVThread::AVThread(QObject *parent) :
|
|
QThread(parent)
|
|
{
|
|
playerID = -1;
|
|
#if (USE_DURATION_IN_THREAD)
|
|
durationSec = 0;
|
|
#endif
|
|
#if (PREVENT_OVER_DURATION_RENDER)
|
|
duration = 0.0;
|
|
#endif
|
|
|
|
connect(this, SIGNAL(started()), SLOT(onStarted()), Qt::DirectConnection);
|
|
connect(this, SIGNAL(finished()), SLOT(onFinished()), Qt::DirectConnection);
|
|
}
|
|
|
|
AVThread::AVThread(AVThreadPrivate &d, QObject *parent)
|
|
:QThread(parent),DPTR_INIT(&d)
|
|
{
|
|
#if (USE_DURATION_IN_THREAD)
|
|
durationSec = 0;
|
|
#endif
|
|
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
endStop = false;
|
|
#endif
|
|
connect(this, SIGNAL(started()), SLOT(onStarted()), Qt::DirectConnection);
|
|
connect(this, SIGNAL(finished()), SLOT(onFinished()), Qt::DirectConnection);
|
|
}
|
|
|
|
AVThread::~AVThread()
|
|
{
|
|
//d_ptr destroyed automatically
|
|
}
|
|
|
|
bool AVThread::isPaused() const
|
|
{
|
|
DPTR_D(const AVThread);
|
|
//if d.next_pause is true, the thread will pause soon, may happens before you can handle the result
|
|
return d.paused || d.next_pause;
|
|
}
|
|
|
|
bool AVThread::installFilter(Filter *filter, int index, bool lock)
|
|
{
|
|
DPTR_D(AVThread);
|
|
int p = index;
|
|
if (p < 0)
|
|
p += d.filters.size();
|
|
if (p < 0)
|
|
p = 0;
|
|
if (p > d.filters.size())
|
|
p = d.filters.size();
|
|
const int old = d.filters.indexOf(filter);
|
|
// already installed at desired position
|
|
if (p == old)
|
|
return true;
|
|
if (lock) {
|
|
QMutexLocker locker(&d.mutex);
|
|
if (p >= 0)
|
|
d.filters.removeAt(p);
|
|
d.filters.insert(p, filter);
|
|
} else {
|
|
if (p >= 0)
|
|
d.filters.removeAt(p);
|
|
d.filters.insert(p, filter);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AVThread::uninstallFilter(Filter *filter, bool lock)
|
|
{
|
|
DPTR_D(AVThread);
|
|
if (lock) {
|
|
QMutexLocker locker(&d.mutex);
|
|
return d.filters.removeOne(filter);
|
|
}
|
|
return d.filters.removeOne(filter);
|
|
}
|
|
|
|
const QList<Filter*>& AVThread::filters() const
|
|
{
|
|
return d_func().filters;
|
|
}
|
|
|
|
void AVThread::scheduleTask(QRunnable *task)
|
|
{
|
|
d_func().tasks.put(task);
|
|
}
|
|
|
|
void AVThread::requestSeek()
|
|
{
|
|
class SeekPTS : public QRunnable {
|
|
AVThread *self;
|
|
public:
|
|
SeekPTS(AVThread* thread) : self(thread) {}
|
|
void run() Q_DECL_OVERRIDE {
|
|
self->d_func().seek_requested = true;
|
|
}
|
|
};
|
|
scheduleTask(new SeekPTS(this));
|
|
}
|
|
|
|
void AVThread::scheduleFrameDrop(bool value)
|
|
{
|
|
class FrameDropTask : public QRunnable {
|
|
AVDecoder *decoder;
|
|
bool drop;
|
|
public:
|
|
FrameDropTask(AVDecoder *dec, bool value) : decoder(dec), drop(value) {}
|
|
void run() Q_DECL_OVERRIDE {
|
|
if (!decoder)
|
|
return;
|
|
if (drop)
|
|
decoder->setOptions(AVThreadPrivate::dec_opt_framedrop);
|
|
else
|
|
decoder->setOptions(AVThreadPrivate::dec_opt_normal);
|
|
}
|
|
};
|
|
scheduleTask(new FrameDropTask(decoder(), value));
|
|
}
|
|
|
|
qreal AVThread::previousHistoryPts() const
|
|
{
|
|
DPTR_D(const AVThread);
|
|
if (d.pts_history.empty()) {
|
|
qDebug("pts history is EMPTY");
|
|
return 0;
|
|
}
|
|
if (d.pts_history.size() == 1)
|
|
return -d.pts_history.back();
|
|
const qreal current_pts = d.pts_history.back();
|
|
for (int i = d.pts_history.size() - 2; i > 0; --i) {
|
|
if (d.pts_history.at(i) < current_pts)
|
|
return d.pts_history.at(i);
|
|
}
|
|
return -d.pts_history.front();
|
|
}
|
|
|
|
qreal AVThread::decodeFrameRate() const
|
|
{
|
|
DPTR_D(const AVThread);
|
|
if (d.pts_history.size() <= 1)
|
|
return 0;
|
|
const qreal dt = d.pts_history.back() - d.pts_history.front();
|
|
if (dt <= 0)
|
|
return 0;
|
|
return d.pts_history.size()/dt;
|
|
}
|
|
|
|
void AVThread::setDropFrameOnSeek(bool value)
|
|
{
|
|
d_func().drop_frame_seek = value;
|
|
}
|
|
|
|
// TODO: shall we close decoder here?
|
|
void AVThread::stop()
|
|
{
|
|
DPTR_D(AVThread);
|
|
d.stop = true; //stop as soon as possible
|
|
QMutexLocker locker(&d.mutex);
|
|
Q_UNUSED(locker);
|
|
d.packets.setBlocking(false); //stop blocking take()
|
|
d.packets.clear();
|
|
pause(false);
|
|
//terminate();
|
|
}
|
|
|
|
// 짧은 동영상 후반부 SEEK 처리시 PAUSED 가 풀림 ->
|
|
// UI 에서 SLIDER MOUSE PRESS 시 PAUSED 상태를 확인할때
|
|
// 이전에 이미 SEEK 가 처리되고 있으면 PAUSED 가 아닌 PLAY 상태로 인식되어
|
|
// SLIDER RELESE 후 다시 PAUSE(false) 처리한 문제였음
|
|
void AVThread::pause(bool p)
|
|
{
|
|
DPTR_D(AVThread);
|
|
if (d.paused == p)
|
|
{
|
|
return;
|
|
}
|
|
// 왜 동일한 PAUSE 가 두번씩 호출되는지 확인
|
|
// RMPlayer::onSliderPress 에서 강제 pause 1차 요청함
|
|
PLAYER_DEBUG_V2("!!!!PAUSE:",50,p,this);
|
|
// if(playerID == 0 && p == true) {
|
|
// int i = 0;
|
|
// qInfo() << i << __FUNCTION__;
|
|
// }
|
|
|
|
d.paused = p;
|
|
if (!d.paused) {
|
|
qDebug("wake up paused thread");
|
|
d.next_pause = false;
|
|
d.cond.wakeAll();
|
|
}
|
|
}
|
|
|
|
void AVThread::nextAndPause()
|
|
{
|
|
DPTR_D(AVThread);
|
|
d.next_pause = true;
|
|
d.paused = true;
|
|
d.cond.wakeAll();
|
|
}
|
|
|
|
void AVThread::lock()
|
|
{
|
|
d_func().mutex.lock();
|
|
}
|
|
|
|
void AVThread::unlock()
|
|
{
|
|
d_func().mutex.unlock();
|
|
}
|
|
|
|
void AVThread::setClock(AVClock *clock)
|
|
{
|
|
d_func().clock = clock;
|
|
}
|
|
|
|
AVClock* AVThread::clock() const
|
|
{
|
|
return d_func().clock;
|
|
}
|
|
|
|
PacketBuffer* AVThread::packetQueue() const
|
|
{
|
|
return const_cast<PacketBuffer*>(&d_func().packets);
|
|
}
|
|
|
|
void AVThread::setDecoder(AVDecoder *decoder)
|
|
{
|
|
DPTR_D(AVThread);
|
|
QMutexLocker lock(&d.mutex);
|
|
Q_UNUSED(lock);
|
|
d.dec = decoder;
|
|
}
|
|
|
|
AVDecoder* AVThread::decoder() const
|
|
{
|
|
return d_func().dec;
|
|
}
|
|
|
|
void AVThread::setOutput(AVOutput *out)
|
|
{
|
|
DPTR_D(AVThread);
|
|
QMutexLocker lock(&d.mutex);
|
|
Q_UNUSED(lock);
|
|
if (!d.outputSet)
|
|
return;
|
|
if (!out) {
|
|
d.outputSet->clearOutputs();
|
|
return;
|
|
}
|
|
d.outputSet->addOutput(out);
|
|
}
|
|
|
|
AVOutput* AVThread::output() const
|
|
{
|
|
DPTR_D(const AVThread);
|
|
if (!d.outputSet || d.outputSet->outputs().isEmpty())
|
|
return 0;
|
|
return d.outputSet->outputs().first();
|
|
}
|
|
|
|
// TODO: remove?
|
|
void AVThread::setOutputSet(OutputSet *set)
|
|
{
|
|
d_func().outputSet = set;
|
|
}
|
|
|
|
OutputSet* AVThread::outputSet() const
|
|
{
|
|
return d_func().outputSet;
|
|
}
|
|
|
|
void AVThread::onStarted()
|
|
{
|
|
d_func().sem.release();
|
|
}
|
|
|
|
void AVThread::onFinished()
|
|
{
|
|
if (d_func().sem.available() > 0)
|
|
d_func().sem.acquire(d_func().sem.available());
|
|
}
|
|
|
|
void AVThread::resetState()
|
|
{
|
|
DPTR_D(AVThread);
|
|
pause(false);
|
|
d.pts_history = ring<qreal>(d.pts_history.capacity());
|
|
d.tasks.clear();
|
|
d.render_pts0 = -1;
|
|
d.stop = false;
|
|
d.packets.setBlocking(true);
|
|
d.packets.clear();
|
|
d.wait_err = 0;
|
|
d.wait_timer.invalidate();
|
|
}
|
|
|
|
bool AVThread::tryPause(unsigned long timeout)
|
|
{
|
|
DPTR_D(AVThread);
|
|
if (!isPaused())
|
|
return false;
|
|
QMutexLocker lock(&d.mutex);
|
|
Q_UNUSED(lock);
|
|
return d.cond.wait(&d.mutex, timeout);
|
|
qDebug("paused thread waked up!!!");
|
|
return true;
|
|
}
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
void AVThread::endStopBy()
|
|
{
|
|
DPTR_D(AVThread);
|
|
QMutexLocker lock(&d.mutex);
|
|
endStop = true;
|
|
}
|
|
#endif
|
|
bool AVThread::processNextTask()
|
|
{
|
|
DPTR_D(AVThread);
|
|
if (d.tasks.isEmpty())
|
|
return true;
|
|
QRunnable *task = d.tasks.take();
|
|
task->run();
|
|
if (task->autoDelete()) {
|
|
delete task;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AVThread::setStatistics(Statistics *statistics)
|
|
{
|
|
DPTR_D(AVThread);
|
|
d.statistics = statistics;
|
|
}
|
|
|
|
bool AVThread::waitForStarted(int msec)
|
|
{
|
|
if (!d_func().sem.tryAcquire(1, msec > 0 ? msec : std::numeric_limits<int>::max()))
|
|
return false;
|
|
d_func().sem.release(1); //ensure another waitForStarted() continues
|
|
return true;
|
|
}
|
|
|
|
void AVThread::waitAndCheck(ulong value, qreal pts)
|
|
{
|
|
DPTR_D(AVThread);
|
|
if (value <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(value > 1000) {
|
|
qInfo() << __FUNCTION__ << __LINE__<< value << "WAIT>>>>>>>";
|
|
}
|
|
|
|
value += d.wait_err;
|
|
d.wait_timer.restart();
|
|
//qDebug("wating for %lu msecs", value);
|
|
ulong us = value * 1000UL;
|
|
const ulong ms = value;
|
|
static const ulong kWaitSlice = K_WAIT_SLICE * 1000UL; //20ms
|
|
while (us > kWaitSlice)
|
|
{
|
|
usleep(kWaitSlice);
|
|
if (d.stop)
|
|
us = 0;
|
|
else
|
|
us -= kWaitSlice;
|
|
if (pts > 0) {
|
|
us = qMin(us, ulong((double)(qMax<qreal>(0, pts - d.clock->value()))*1000000.0));
|
|
}
|
|
//qDebug("us: %lu/%lu, pts: %f, clock: %f", us, ms-et.elapsed(), pts, d.clock->value());
|
|
processNextTask();
|
|
us = qMin<ulong>(us, (ms-d.wait_timer.elapsed())*1000);
|
|
}
|
|
if (us > 0)
|
|
{
|
|
usleep(us);
|
|
// if(us > 100000) {
|
|
// qInfo() << "USLEEP!_____________" << us << __FUNCTION__ << __LINE__;
|
|
// }
|
|
}
|
|
//qDebug("wait elapsed: %lu %d/%lld", us, ms, et.elapsed());
|
|
const int de = ((ms-d.wait_timer.elapsed()) - d.wait_err);
|
|
if (de > -3 && de < 3)
|
|
{
|
|
d.wait_err += de;
|
|
}
|
|
else
|
|
{
|
|
d.wait_err += de > 0 ? 1 : -1;
|
|
}
|
|
//qDebug("err: %lld", d.wait_err);
|
|
}
|
|
|
|
} //namespace FAV
|