1050 lines
33 KiB
C++
1050 lines
33 KiB
C++
/******************************************************************************
|
|
QtAV: Multimedia framework based on Qt and FFmpeg
|
|
Copyright (C) 2012-2017 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 "AVDemuxThread.h"
|
|
#include <limits>
|
|
#include "AVClock.h"
|
|
#include "AVDemuxer.h"
|
|
#include "AVDecoder.h"
|
|
#include "VideoThread.h"
|
|
#include "AudioThread.h"
|
|
#include <QtCore/QTime>
|
|
#include "Logger.h"
|
|
//#include "../../roadmovie/rm_constants.h"
|
|
|
|
#if (RM_TESTING)
|
|
#include "../tester/rm_test_dialog.h"
|
|
#endif
|
|
|
|
#define RESUME_ONCE_ON_SEEK 0
|
|
|
|
|
|
#define DEMUX_THREAD_DEBUG 1
|
|
|
|
namespace FAV {
|
|
|
|
class AutoSem {
|
|
QSemaphore *s;
|
|
public:
|
|
AutoSem(QSemaphore* sem) : s(sem) { s->release();}
|
|
~AutoSem() {
|
|
if (s->available() > 0)
|
|
s->acquire(s->available());
|
|
}
|
|
};
|
|
|
|
class QueueEmptyCall : public PacketBuffer::StateChangeCallback
|
|
{
|
|
public:
|
|
QueueEmptyCall(AVDemuxThread* thread):
|
|
mDemuxThread(thread)
|
|
{}
|
|
virtual void call() {
|
|
if (!mDemuxThread)
|
|
return;
|
|
if (mDemuxThread->isEnd())
|
|
return;
|
|
if (mDemuxThread->atEndOfMedia())
|
|
return;
|
|
mDemuxThread->updateBufferState(); // ensure detect buffering immediately
|
|
AVThread *thread = mDemuxThread->videoThread();
|
|
//qDebug("try wake up video queue");
|
|
if (thread)
|
|
thread->packetQueue()->blockFull(false);
|
|
//qDebug("try wake up audio queue");
|
|
thread = mDemuxThread->audioThread();
|
|
if (thread)
|
|
thread->packetQueue()->blockFull(false);
|
|
}
|
|
private:
|
|
AVDemuxThread *mDemuxThread;
|
|
};
|
|
|
|
AVDemuxThread::AVDemuxThread(QObject *parent) :
|
|
QThread(parent)
|
|
, paused(false)
|
|
, user_paused(false)
|
|
, end(false)
|
|
, end_action(MediaEndAction_Default)
|
|
, m_buffering(false)
|
|
, m_buffer(0)
|
|
, demuxer(0)
|
|
, ademuxer(0)
|
|
, audio_thread(0)
|
|
, video_thread(0)
|
|
, clock_type(-1)
|
|
{
|
|
seek_tasks.setCapacity(1);
|
|
seek_tasks.blockFull(false);
|
|
}
|
|
|
|
AVDemuxThread::AVDemuxThread(AVDemuxer *dmx, QObject *parent) :
|
|
QThread(parent)
|
|
, paused(false)
|
|
, end(false)
|
|
, m_buffering(false)
|
|
, m_buffer(0)
|
|
, audio_thread(0)
|
|
, video_thread(0)
|
|
, playerID(-1)
|
|
{
|
|
setDemuxer(dmx);
|
|
seek_tasks.setCapacity(1);
|
|
seek_tasks.blockFull(false);
|
|
}
|
|
|
|
void AVDemuxThread::setDemuxer(AVDemuxer *dmx)
|
|
{
|
|
demuxer = dmx;
|
|
}
|
|
|
|
void AVDemuxThread::setAudioDemuxer(AVDemuxer *demuxer)
|
|
{
|
|
//QMutexLocker locker(&buffer_mutex);
|
|
//Q_UNUSED(locker);
|
|
ademuxer = demuxer;
|
|
}
|
|
|
|
void AVDemuxThread::setAVThread(AVThread*& pOld, AVThread *pNew)
|
|
{
|
|
if (pOld == pNew)
|
|
return;
|
|
if (pOld) {
|
|
if (pOld->isRunning())
|
|
pOld->stop();
|
|
pOld->disconnect(this, SLOT(onAVThreadQuit()));
|
|
}
|
|
pOld = pNew;
|
|
if (!pNew)
|
|
return;
|
|
pOld->packetQueue()->setEmptyCallback(new QueueEmptyCall(this));
|
|
connect(pOld, SIGNAL(finished()), SLOT(onAVThreadQuit()));
|
|
}
|
|
|
|
void AVDemuxThread::setAudioThread(AVThread *thread)
|
|
{
|
|
setAVThread(audio_thread, thread);
|
|
}
|
|
|
|
void AVDemuxThread::setVideoThread(AVThread *thread)
|
|
{
|
|
setAVThread(video_thread, thread);
|
|
}
|
|
|
|
AVThread* AVDemuxThread::videoThread()
|
|
{
|
|
return video_thread;
|
|
}
|
|
|
|
AVThread* AVDemuxThread::audioThread()
|
|
{
|
|
return audio_thread;
|
|
}
|
|
|
|
/*
|
|
void AVDemuxThread::stepBackward()
|
|
{
|
|
if (!video_thread)
|
|
return;
|
|
AVThread *t = video_thread;
|
|
const qreal pre_pts = video_thread->previousHistoryPts();
|
|
if (pre_pts == 0.0) {
|
|
qWarning("can not get previous pts");
|
|
return;
|
|
}
|
|
end = false;
|
|
// queue maybe blocked by put()
|
|
if (audio_thread) {
|
|
audio_thread->packetQueue()->clear(); // will put new packets before task run
|
|
}
|
|
|
|
class stepBackwardTask : public QRunnable {
|
|
public:
|
|
stepBackwardTask(AVDemuxThread *dt, qreal t)
|
|
: demux_thread(dt)
|
|
, pts(t)
|
|
{}
|
|
void run() {
|
|
AVThread *avt = demux_thread->videoThread();
|
|
avt->packetQueue()->clear(); // clear here
|
|
if (pts <= 0) {
|
|
demux_thread->demuxer->seek(qint64(-pts*1000.0) - 500LL);
|
|
QVector<qreal> ts;
|
|
qreal t = -1.0;
|
|
while (t < -pts) {
|
|
demux_thread->demuxer->readFrame();
|
|
if (demux_thread->demuxer->stream() != demux_thread->demuxer->videoStream())
|
|
continue;
|
|
t = demux_thread->demuxer->packet().pts;
|
|
ts.push_back(t);
|
|
}
|
|
const qreal t0 = ts.back();
|
|
ts.pop_back();
|
|
const qreal dt = t0 - ts.back();
|
|
pts = ts.back();
|
|
// FIXME: sometimes can not seek to the previous pts, the result pts is always current pts, so let the target pts a little earlier
|
|
pts -= dt/2.0;
|
|
}
|
|
qDebug("step backward: %lld, %f", qint64(pts*1000.0), pts);
|
|
demux_thread->video_thread->setDropFrameOnSeek(false);
|
|
demux_thread->seekInternal(qint64(pts*1000.0), AccurateSeek);
|
|
}
|
|
private:
|
|
AVDemuxThread *demux_thread;
|
|
qreal pts;
|
|
};
|
|
|
|
pause(true);
|
|
t->packetQueue()->clear(); // will put new packets before task run
|
|
t->packetQueue();
|
|
Packet pkt;
|
|
pkt.pts = pre_pts;
|
|
t->packetQueue()->put(pkt); // clear and put a seek packet to ensure not frames other than previous frame will be decoded and rendered
|
|
video_thread->pause(false);
|
|
newSeekRequest(new stepBackwardTask(this, pre_pts));
|
|
}
|
|
*/
|
|
void AVDemuxThread::seek(qint64 pos, SeekType type)
|
|
{
|
|
PLAYER_DEBUG_V("@@@SEEK_POS:",50,pos);
|
|
|
|
end = false;
|
|
// queue maybe blocked by put()
|
|
if (audio_thread) {
|
|
audio_thread->packetQueue()->clear();
|
|
}
|
|
if (video_thread) {
|
|
video_thread->packetQueue()->clear();
|
|
}
|
|
class SeekTask : public QRunnable {
|
|
public:
|
|
SeekTask(AVDemuxThread *dt, qint64 t, SeekType st)
|
|
: demux_thread(dt)
|
|
, type(st)
|
|
, position(t)
|
|
{}
|
|
void run() {
|
|
if (demux_thread->video_thread) {
|
|
demux_thread->video_thread->setDropFrameOnSeek(true);
|
|
}
|
|
demux_thread->seekInternal(position, type);
|
|
}
|
|
private:
|
|
AVDemuxThread *demux_thread;
|
|
SeekType type;
|
|
qint64 position;
|
|
};
|
|
newSeekRequest(new SeekTask(this, pos, type));
|
|
}
|
|
|
|
void AVDemuxThread::seekInternal(qint64 pos, SeekType type)
|
|
{
|
|
AVThread* av[] = { audio_thread, video_thread};
|
|
//qDebug("seek to %s %lld ms (%f%%)", QTime(0, 0, 0).addMSecs(pos).toString().toUtf8().constData(), pos, double(pos - demuxer->startTime())/double(demuxer->duration())*100.0);
|
|
demuxer->setSeekType(type);
|
|
|
|
#if (UPDATE_LAST_PACKET_DURATION)
|
|
// OVER SEEK
|
|
if(!demuxer->seek(pos) && pos * 1000 > demuxer->durationFixed) {
|
|
pos = (demuxer->durationFixed / 1000) - 100;
|
|
}
|
|
#else // UPDATE_LAST_PACKET_DURATION
|
|
// AVDemuxer::seek 에서
|
|
// av_seek_frame 호출함
|
|
demuxer->seek(pos);
|
|
#endif // UPDATE_LAST_PACKET_DURATION
|
|
|
|
if (ademuxer) {
|
|
ademuxer->setSeekType(type);
|
|
ademuxer->seek(pos);
|
|
}
|
|
|
|
AVThread *watch_thread = 0;
|
|
// TODO: why queue may not empty?
|
|
int sync_id = 0;
|
|
// Audio, Video Thread for 처리
|
|
for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) {
|
|
AVThread *t = av[i];
|
|
if (!t) {
|
|
continue;
|
|
}
|
|
if (!sync_id) {
|
|
sync_id = t->clock()->syncStart(!!audio_thread + (!!video_thread && !demuxer->hasAttacedPicture()));
|
|
// 최초 SYNC 시 2가 리턴되며 계속 증가함
|
|
PLAYER_DEBUG_V("@@@SEEK_SYNC_ID:",90,sync_id);
|
|
}
|
|
Q_ASSERT(sync_id != 0);
|
|
qDebug("demuxer sync id: %d/%d", sync_id, t->clock()->syncId());
|
|
t->packetQueue()->clear(); // PACKET QUEUE 제거
|
|
t->requestSeek(); // 각 thread 에 seek 요구
|
|
// TODO: the first frame (key frame) will not be decoded correctly if flush() is called.
|
|
//PacketBuffer *pb = t->packetQueue();
|
|
//qDebug("%s put seek packet. %d/%d-%.3f, progress: %.3f", t->metaObject()->className(), pb->buffered(), pb->bufferValue(), pb->bufferMax(), pb->bufferProgress());
|
|
t->packetQueue()->setBlocking(false); // aqueue bufferValue can be small (1), we can not put and take
|
|
|
|
// 빈(데이터가 없는) 패킷을 생성하여 위치만 지정하여 추가한다
|
|
Packet pkt;
|
|
#if (PLAY_SYNC_FIX2)
|
|
pkt._isSeek = true;
|
|
#endif // PLAY_SYNC_FIX2
|
|
pkt.pts = qreal(pos)/1000.0;
|
|
|
|
#if (REAR_SYNC_FRONT)
|
|
if(playerID == 1 && rear_delay > 0.0)
|
|
{
|
|
//qInfo() << "SEEK DELAY pkt.pts:" << pkt.pts << " rear delay:" << rear_delay << " = " << pkt.pts-rear_delay << "pkt.position:" << sync_id;
|
|
pkt.pts = qMax(0.0,pkt.pts-rear_delay);
|
|
}
|
|
#endif // REAR_SYNC_FRONT
|
|
|
|
pkt.position = sync_id;
|
|
t->packetQueue()->put(pkt);
|
|
PLAYER_DEBUG_V("@@@SEEK_PACKET_AFTER_PUT SIZE:",100,t->packetQueue()->size());
|
|
t->packetQueue()->setBlocking(true); // blockEmpty was false when eof is read.
|
|
if (isPaused()) { //TODO: deal with pause in AVThread?
|
|
t->pause(false);
|
|
watch_thread = t;
|
|
//qInfo() << "is paused";
|
|
}
|
|
}
|
|
if (watch_thread) {
|
|
pauseInternal(false);
|
|
Q_EMIT requestClockPause(false); // need direct connection
|
|
// direct connection is fine here
|
|
#if (PLAY_SYNC_FIX2)
|
|
connect(watch_thread, SIGNAL(seekFinished(qint64,qint64)), this, SLOT(seekOnPauseFinished()), Qt::DirectConnection);
|
|
#else
|
|
connect(watch_thread, SIGNAL(seekFinished(qint64)), this, SLOT(seekOnPauseFinished()), Qt::DirectConnection);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void AVDemuxThread::newSeekRequest(QRunnable *r)
|
|
{
|
|
if (seek_tasks.size() >= seek_tasks.capacity()) {
|
|
QRunnable *r = seek_tasks.take();
|
|
if (r && r->autoDelete())
|
|
delete r;
|
|
}
|
|
seek_tasks.put(r);
|
|
}
|
|
|
|
void AVDemuxThread::processNextSeekTask()
|
|
{
|
|
if (seek_tasks.isEmpty())
|
|
return;
|
|
QRunnable *task = seek_tasks.take();
|
|
if (!task)
|
|
return;
|
|
task->run();
|
|
if (task->autoDelete())
|
|
delete task;
|
|
}
|
|
|
|
void AVDemuxThread::pauseInternal(bool value)
|
|
{
|
|
paused = value;
|
|
}
|
|
|
|
bool AVDemuxThread::isPaused() const
|
|
{
|
|
return paused;
|
|
}
|
|
|
|
bool AVDemuxThread::isEnd() const
|
|
{
|
|
return end;
|
|
}
|
|
|
|
bool AVDemuxThread::atEndOfMedia() const
|
|
{
|
|
return demuxer->atEnd();
|
|
}
|
|
|
|
PacketBuffer* AVDemuxThread::buffer()
|
|
{
|
|
return m_buffer;
|
|
}
|
|
|
|
void AVDemuxThread::updateBufferState()
|
|
{
|
|
if (!m_buffer)
|
|
return;
|
|
if (m_buffering) { // always report progress when buffering
|
|
Q_EMIT bufferProgressChanged(m_buffer->bufferProgress());
|
|
}
|
|
if (m_buffering == m_buffer->isBuffering())
|
|
return;
|
|
m_buffering = m_buffer->isBuffering();
|
|
Q_EMIT mediaStatusChanged(m_buffering ? FAV::BufferingMedia : FAV::BufferedMedia);
|
|
// state change to buffering, report progress immediately. otherwise we have to wait to read 1 packet.
|
|
if (m_buffering) {
|
|
Q_EMIT bufferProgressChanged(m_buffer->bufferProgress());
|
|
}
|
|
}
|
|
|
|
//No more data to put. So stop blocking the queue to take the reset elements
|
|
void AVDemuxThread::stop()
|
|
{
|
|
//this will not affect the pause state if we pause the output
|
|
//TODO: why remove blockFull(false) can not play another file?
|
|
AVThread* av[] = { audio_thread, video_thread};
|
|
for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) {
|
|
AVThread* t = av[i];
|
|
if (!t)
|
|
continue;
|
|
t->packetQueue()->clear();
|
|
t->packetQueue()->blockFull(false); //??
|
|
while (t->isRunning())
|
|
{
|
|
qDebug() << "stopping thread " << t;
|
|
t->stop();
|
|
t->wait(500);
|
|
}
|
|
}
|
|
pause(false);
|
|
cond.wakeAll();
|
|
qDebug("all avthread finished. try to exit demux thread<<<<<<");
|
|
end = true;
|
|
}
|
|
|
|
void AVDemuxThread::pause(bool p, bool wait)
|
|
{
|
|
user_paused = p;
|
|
if (paused == p)
|
|
return;
|
|
paused = p;
|
|
|
|
if (!paused)
|
|
cond.wakeAll();
|
|
else {
|
|
if (wait) {
|
|
// block until current loop finished
|
|
buffer_mutex.lock();
|
|
buffer_mutex.unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AVDemuxThread::setMediaEndAction(MediaEndAction value)
|
|
{
|
|
end_action = value;
|
|
}
|
|
|
|
MediaEndAction AVDemuxThread::mediaEndAction() const
|
|
{
|
|
return end_action;
|
|
}
|
|
/*
|
|
void AVDemuxThread::stepForward()
|
|
{
|
|
if (end)
|
|
return;
|
|
// clock type will be wrong if no lock because slot frameDeliveredOnStepForward() is in video thread
|
|
QMutexLocker locker(&next_frame_mutex);
|
|
Q_UNUSED(locker);
|
|
pause(true); // must pause AVDemuxThread (set user_paused true)
|
|
AVThread* av[] = {video_thread, audio_thread};
|
|
bool connected = false;
|
|
for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) {
|
|
AVThread *t = av[i];
|
|
if (!t)
|
|
continue;
|
|
// set clock first
|
|
if (clock_type < 0)
|
|
clock_type = (int)t->clock()->isClockAuto() + 2*(int)t->clock()->clockType();
|
|
t->clock()->setClockType(AVClock::VideoClock);
|
|
t->scheduleFrameDrop(false);
|
|
t->pause(false);
|
|
t->packetQueue()->blockFull(false);
|
|
if (!connected) {
|
|
connect(t, SIGNAL(frameDelivered()), this, SLOT(frameDeliveredOnStepForward()), Qt::DirectConnection);
|
|
connect(t, SIGNAL(eofDecoded()), this, SLOT(eofDecodedOnStepForward()), Qt::DirectConnection);
|
|
connected = true;
|
|
}
|
|
}
|
|
Q_EMIT requestClockPause(false);
|
|
pauseInternal(false);
|
|
}
|
|
*/
|
|
void AVDemuxThread::seekOnPauseFinished()
|
|
{
|
|
AVThread *thread = video_thread ? video_thread : audio_thread;
|
|
Q_ASSERT(thread);
|
|
#if (PLAY_SYNC_FIX2)
|
|
disconnect(thread, SIGNAL(seekFinished(qint64,qint64)), this, SLOT(seekOnPauseFinished()));
|
|
#else
|
|
disconnect(thread, SIGNAL(seekFinished(qint64)), this, SLOT(seekOnPauseFinished()));
|
|
#endif
|
|
if (user_paused) {
|
|
pause(true); // restore pause state
|
|
Q_EMIT requestClockPause(true); // need direct connection
|
|
// pause video/audio thread
|
|
if (video_thread)
|
|
video_thread->pause(true);
|
|
if (audio_thread)
|
|
audio_thread->pause(true);
|
|
}
|
|
}
|
|
|
|
//void AVDemuxThread::frameDeliveredOnStepForward()
|
|
//{
|
|
// AVThread *thread = video_thread ? video_thread : audio_thread;
|
|
// Q_ASSERT(thread);
|
|
// QMutexLocker locker(&next_frame_mutex);
|
|
// Q_UNUSED(locker);
|
|
// disconnect(thread, SIGNAL(frameDelivered()), this, SLOT(frameDeliveredOnStepForward()));
|
|
// disconnect(thread, SIGNAL(eofDecoded()), this, SLOT(eofDecodedOnStepForward()));
|
|
// if (user_paused) {
|
|
// pause(true); // restore pause state
|
|
// Q_EMIT requestClockPause(true); // need direct connection
|
|
// // pause both video and audio thread
|
|
// if (video_thread)
|
|
// video_thread->pause(true);
|
|
// if (audio_thread)
|
|
// audio_thread->pause(true);
|
|
// }
|
|
// if (clock_type >= 0) {
|
|
// thread->clock()->setClockAuto(clock_type & 1);
|
|
// thread->clock()->setClockType(AVClock::ClockType(clock_type/2));
|
|
// clock_type = -1;
|
|
// thread->clock()->updateExternalClock((thread->previousHistoryPts() - thread->clock()->initialValue())*1000.0);
|
|
// }
|
|
// Q_EMIT stepFinished();
|
|
//}
|
|
|
|
//void AVDemuxThread::eofDecodedOnStepForward()
|
|
//{
|
|
// AVThread *thread = video_thread ? video_thread : audio_thread;
|
|
// Q_ASSERT(thread);
|
|
// QMutexLocker locker(&next_frame_mutex);
|
|
// Q_UNUSED(locker);
|
|
// disconnect(thread, SIGNAL(frameDelivered()), this, SLOT(frameDeliveredOnStepForward()));
|
|
// disconnect(thread, SIGNAL(eofDecoded()), this, SLOT(eofDecodedOnStepForward()));
|
|
// pause(false);
|
|
// end = true;
|
|
// if (clock_type >= 0) {
|
|
// thread->clock()->setClockAuto(clock_type & 1);
|
|
// thread->clock()->setClockType(AVClock::ClockType(clock_type/2));
|
|
// clock_type = -1;
|
|
// }
|
|
// Q_EMIT stepFinished();
|
|
//}
|
|
|
|
void AVDemuxThread::onAVThreadQuit()
|
|
{
|
|
AVThread* av[] = { audio_thread, video_thread};
|
|
for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) {
|
|
if (!av[i])
|
|
continue;
|
|
if (av[i]->isRunning())
|
|
return;
|
|
}
|
|
end = true; //(!audio_thread || !audio_thread->isRunning()) &&
|
|
}
|
|
|
|
bool AVDemuxThread::waitForStarted(int msec)
|
|
{
|
|
if (!sem.tryAcquire(1, msec > 0 ? msec : std::numeric_limits<int>::max()))
|
|
return false;
|
|
sem.release(1); //ensure other waitForStarted() calls continue
|
|
return true;
|
|
}
|
|
|
|
void AVDemuxThread::run()
|
|
{
|
|
PLAYER_DEBUG("@@@DEMUX_THREAD_START",100);
|
|
|
|
#if (FORCE_BREAK_EOF_LOG)
|
|
qInfo() << "-------------------------------------------------------------------";
|
|
qInfo() << __FUNCTION__ << __LINE__ << playerID;
|
|
#endif
|
|
|
|
// 종료 에러 확인용
|
|
#if ((RM_TESTING || PLAY_SYNC_FIX2) && VTHREAD_DEBUG_ON)
|
|
g_et.start();
|
|
#endif
|
|
|
|
m_buffering = false;
|
|
end = false;
|
|
if (audio_thread)
|
|
{
|
|
//#if (FORCE_BREAK_EOF)
|
|
// // 이전 재생 정지
|
|
// if(audio_thread->isRunning())
|
|
// {
|
|
|
|
// audio_thread->forceEnd = true;
|
|
// audio_thread->stop();
|
|
// while (audio_thread->isRunning()) {
|
|
// msleep(10);
|
|
// }
|
|
// }
|
|
//#endif
|
|
|
|
//QThread::HighPriority, QThread::HighPriority
|
|
if(!audio_thread->isRunning())
|
|
{
|
|
audio_thread->start(QThread::HighPriority); // QThread::HighPriority / HighestPriority
|
|
}
|
|
}
|
|
if (video_thread)
|
|
{
|
|
//#if (FORCE_BREAK_EOF)
|
|
// if(video_thread->isRunning())
|
|
// {
|
|
// video_thread->forceEnd = true;
|
|
// video_thread->stop();
|
|
// while (video_thread->isRunning()) {
|
|
// msleep(10);
|
|
// }
|
|
// }
|
|
//#endif
|
|
if(!video_thread->isRunning())
|
|
{
|
|
video_thread->start();
|
|
}
|
|
}
|
|
#if (FIXED_FPS_DURATION)
|
|
// 종료 지점 설정
|
|
// qInfo() << "SET STOP POSITON:" << demuxer->durationFixed;
|
|
#if (REAR_SYNC_FRONT)
|
|
if(playerID == 1)
|
|
{
|
|
((VideoThread*)video_thread)->stopPosition = demuxer->durationFixed + (demuxer->rear_delay * 1000000);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
((VideoThread*)video_thread)->stopPosition = demuxer->durationFixed;
|
|
}
|
|
if(playerID == 0 && audio_thread != NULL)
|
|
{
|
|
((AudioThread*)audio_thread)->stopPosition = demuxer->durationFixed - 300000; // 0.3 초
|
|
}
|
|
#endif
|
|
|
|
#if (USE_DURATION_IN_THREAD)
|
|
double durationSec = ((double)demuxer->durationUs()) / 1000000.0;
|
|
video_thread->durationSec = durationSec;
|
|
if(audio_thread != NULL)
|
|
{
|
|
audio_thread->durationSec = durationSec;
|
|
}
|
|
#endif
|
|
|
|
int stream = 0;
|
|
Packet pkt;
|
|
pause(false);
|
|
#if !(OFF_OTHER_DEBUG)
|
|
qDebug("get av queue a/v thread = %p %p", audio_thread, video_thread);
|
|
#endif
|
|
|
|
PacketBuffer *aqueue = audio_thread ? audio_thread->packetQueue() : 0;
|
|
PacketBuffer *vqueue = video_thread ? video_thread->packetQueue() : 0;
|
|
// aqueue as a primary buffer: music with/without cover
|
|
AVThread* thread = !video_thread || (audio_thread && demuxer->hasAttacedPicture()) ? audio_thread : video_thread;
|
|
m_buffer = thread->packetQueue();
|
|
const qint64 buf2 = aqueue ? aqueue->bufferValue() : 1; // TODO: may be changed by user. Deal with audio track change
|
|
if (aqueue)
|
|
{
|
|
aqueue->clear();
|
|
aqueue->setBlocking(true);
|
|
}
|
|
if (vqueue)
|
|
{
|
|
vqueue->clear();
|
|
vqueue->setBlocking(true);
|
|
}
|
|
m_buffer->clear();
|
|
#if (PLAY_SYNC_FIX2)
|
|
connect(thread, SIGNAL(seekFinished(qint64,qint64)), this, SIGNAL(seekFinished(qint64,qint64)), Qt::DirectConnection);
|
|
#else
|
|
connect(thread, SIGNAL(seekFinished(qint64)), this, SIGNAL(seekFinished(qint64)), Qt::DirectConnection);
|
|
#endif
|
|
seek_tasks.clear();
|
|
int was_end = 0;
|
|
if (ademuxer)
|
|
{
|
|
ademuxer->seek(0LL);
|
|
}
|
|
qreal last_apts = 0;
|
|
qreal last_vpts = 0;
|
|
|
|
AutoSem as(&sem);
|
|
Q_UNUSED(as);
|
|
while (!end)
|
|
{
|
|
processNextSeekTask();
|
|
//vthread maybe changed by AVPlayer.setPriority() from no dec case
|
|
vqueue = video_thread ? video_thread->packetQueue() : 0;
|
|
// 12초 5FPS 영상의 경우 거의 즉시 모든 프레임을 읽고(readFrame)
|
|
// packetQueue 에 모두 put 처리하여 atEnd 상태가 됨
|
|
if (demuxer->atEnd())
|
|
{
|
|
// if avthread may skip 1st eof packet because of a/v sync
|
|
const int kMaxEof = 1;//if buffer packet, we can use qMax(aqueue->bufferValue(), vqueue->bufferValue()) and not call blockEmpty(false);
|
|
if (aqueue && (!was_end || aqueue->isEmpty()))
|
|
{
|
|
if (was_end < kMaxEof)
|
|
{
|
|
aqueue->put(Packet::createEOF());
|
|
}
|
|
const qreal dpts = last_vpts - last_apts;
|
|
if (dpts > 0.1)
|
|
{
|
|
Packet fake_apkt;
|
|
fake_apkt.duration = last_vpts - qMin(thread->clock()->videoTime(), thread->clock()->value()); // FIXME: when clock value < 0?
|
|
#if !(OFF_OTHER_DEBUG)
|
|
qDebug("audio is too short than video: %.3f, fake_apkt.duration: %.3f", dpts, fake_apkt.duration);
|
|
#endif
|
|
last_apts = last_vpts = 0; // if not reset to 0, for example real eof pts, then no fake apkt after seek because dpts < 0
|
|
aqueue->put(fake_apkt);
|
|
}
|
|
aqueue->blockEmpty(was_end >= kMaxEof); // do not block if buffer is not enough. block again on seek
|
|
}
|
|
if (vqueue && (!was_end || vqueue->isEmpty()))
|
|
{
|
|
if (was_end < kMaxEof)
|
|
{
|
|
vqueue->put(Packet::createEOF());
|
|
//PLAYER_DEBUG_V("@@@CREATE_EOF LAST/VPTS:",60,last_vpts);
|
|
} else {
|
|
//PLAYER_DEBUG("@@@NO_EOF",50);
|
|
}
|
|
//
|
|
vqueue->blockEmpty(was_end >= kMaxEof);
|
|
}
|
|
if (m_buffering)
|
|
{
|
|
m_buffering = false;
|
|
Q_EMIT mediaStatusChanged(FAV::BufferedMedia);
|
|
}
|
|
was_end = qMin(was_end + 1, kMaxEof);
|
|
bool exit_thread = !user_paused;
|
|
if (aqueue)
|
|
{
|
|
#if (FORCE_BREAK_EOF && !PLAY_SYNC_FIX2)
|
|
exit_thread &= (aqueue->isEmpty() || (aqueue->size() == 1 && aqueue->take().isEOF()));
|
|
#else
|
|
exit_thread &= aqueue->isEmpty();
|
|
#endif
|
|
}
|
|
if (vqueue)
|
|
{
|
|
// 비워지지 않으면 종료되지 않는다.
|
|
#if (FORCE_BREAK_EOF && !PLAY_SYNC_FIX2)
|
|
exit_thread &= (vqueue->isEmpty() || (vqueue->size() == 1 && vqueue->take().isEOF()));
|
|
#else
|
|
exit_thread &= vqueue->isEmpty();
|
|
#endif
|
|
}
|
|
if (exit_thread)
|
|
{
|
|
#if !(PLAY_SYNC_FIX2)
|
|
if(video_thread)
|
|
{
|
|
video_thread->forceEnd = true;
|
|
}
|
|
if(audio_thread)
|
|
{
|
|
audio_thread->forceEnd = true;
|
|
}
|
|
#endif // PLAY_SYNC_FIX2
|
|
|
|
FIXED_SLEEP; //qInfo() << "EXIT THREAD" << playerID << __FUNCTION__ << __LINE__ << et.elapsed();
|
|
|
|
#if !(PLAY_SYNC_FIX2)
|
|
if (!(mediaEndAction() & MediaEndAction_Pause))
|
|
{
|
|
break;
|
|
}
|
|
#endif // #if !(PLAY_SYNC_FIX2)
|
|
#if !(PLAY_SYNC_FIX2)
|
|
// 정지할 경우 CLOCK 으로 업데이트가 불가능함...
|
|
pause(true);
|
|
Q_EMIT requestClockPause(true);
|
|
if (aqueue)
|
|
{
|
|
aqueue->blockEmpty(true);
|
|
}
|
|
if (vqueue)
|
|
{
|
|
vqueue->blockEmpty(true);
|
|
}
|
|
#endif // // PLAY_SYNC_FIX2
|
|
}
|
|
else
|
|
{
|
|
if(video_thread)
|
|
{
|
|
video_thread->aboutToEnd = true;
|
|
}
|
|
if(audio_thread)
|
|
{
|
|
audio_thread->aboutToEnd = true;
|
|
}
|
|
}
|
|
// wait for a/v thread finished
|
|
msleep(200);
|
|
PLAYER_DEBUG("@@@END_CONTINUE",10);
|
|
continue;
|
|
} // if (demuxer->atEnd())
|
|
|
|
if (demuxer->mediaStatus() == StalledMedia)
|
|
{
|
|
#if !(OFF_OTHER_DEBUG)
|
|
qDebug("stalled media. exiting demuxing thread");
|
|
#endif
|
|
break;
|
|
}
|
|
was_end = 0;
|
|
if (tryPause())
|
|
{
|
|
PLAYER_DEBUG("@@@TRY_PAUSE_CONTINUE",10);
|
|
continue; //the queue is empty and will block
|
|
}
|
|
#if !(FORCE_BREAK_EOF)
|
|
updateBufferState();
|
|
#endif
|
|
if (!demuxer->readFrame())
|
|
{
|
|
continue;
|
|
}
|
|
stream = demuxer->stream();
|
|
pkt = demuxer->packet();
|
|
|
|
Packet apkt;
|
|
bool audio_has_pic = demuxer->hasAttacedPicture();
|
|
int a_ext = 0;
|
|
if (ademuxer) {
|
|
QMutexLocker locker(&buffer_mutex);
|
|
Q_UNUSED(locker);
|
|
if (ademuxer) {
|
|
a_ext = -1;
|
|
audio_has_pic = ademuxer->hasAttacedPicture();
|
|
// FIXME: buffer full but buffering!!!
|
|
// avoid read external track everytime. aqueue may not block full
|
|
// vqueue will not block if aqueue is not enough
|
|
if (!aqueue->isFull() || aqueue->isBuffering()) {
|
|
if (ademuxer->readFrame()) {
|
|
if (ademuxer->stream() == ademuxer->audioStream()) {
|
|
a_ext = 1;
|
|
apkt = ademuxer->packet();
|
|
}
|
|
}
|
|
// no continue otherwise. ademuxer finished earlier than demuxer
|
|
}
|
|
}
|
|
}
|
|
|
|
//qDebug("vqueue: %d, aqueue: %d/isbuffering %d isfull: %d, buffer: %d/%d", vqueue->size(), aqueue->size(), aqueue->isBuffering(), aqueue->isFull(), aqueue->buffered(), aqueue->bufferValue());
|
|
|
|
//QMutexLocker locker(&buffer_mutex); //TODO: seems we do not need to lock
|
|
//Q_UNUSED(locker);
|
|
/*1 is empty but another is enough, then do not block to
|
|
ensure the empty one can put packets immediatly.
|
|
But usually it will not happen, why?
|
|
*/
|
|
/* demux thread will be blocked only when 1 queue is full and still put
|
|
* if vqueue is full and aqueue becomes empty, then demux thread
|
|
* will be blocked. so we should wake up another queue when empty(or threshold?).
|
|
* TODO: the video stream and audio stream may be group by group. provide it
|
|
* stream data: aaaaaaavvvvvvvaaaaaaaavvvvvvvvvaaaaaa, it happens
|
|
* stream data: aavavvavvavavavavavavavavvvaavavavava, it's ok
|
|
*/
|
|
//TODO: use cache queue, take from cache queue if not empty?
|
|
const bool a_internal = stream == demuxer->audioStream();
|
|
if (a_internal || a_ext > 0) {//apkt.isValid()) {
|
|
if (a_internal && !a_ext) // internal is always read even if external audio used
|
|
apkt = demuxer->packet();
|
|
last_apts = apkt.pts;
|
|
/* if vqueue if not blocked and full, and aqueue is empty, then put to
|
|
* vqueue will block demuex thread
|
|
*/
|
|
if (aqueue)
|
|
{
|
|
if (!audio_thread || !audio_thread->isRunning()) {
|
|
aqueue->clear();
|
|
continue;
|
|
}
|
|
// must ensure bufferValue set correctly before continue
|
|
if (m_buffer != aqueue)
|
|
{
|
|
aqueue->setBufferValue(m_buffer->isBuffering() ? std::numeric_limits<qint64>::max() : buf2);
|
|
}
|
|
// always block full if no vqueue because empty callback may set false
|
|
// attached picture is cover for song, 1 frame
|
|
aqueue->blockFull(!video_thread || !video_thread->isRunning() || !vqueue || audio_has_pic);
|
|
// external audio: a_ext < 0, stream = audio_idx=>put invalid packet
|
|
#if (FORCE_BREAK_EOF)
|
|
// 여기서 종료 시 에러 발생함. (end 확인해서 PUT 하지 않도록 해야함)
|
|
if (a_ext >= 0 && end == false)
|
|
#else
|
|
if (a_ext >= 0)
|
|
#endif
|
|
{
|
|
aqueue->put(apkt); //affect video_thread
|
|
}
|
|
}
|
|
}
|
|
// always check video stream if use external audio
|
|
if (stream == demuxer->videoStream()) {
|
|
|
|
if (vqueue)
|
|
{
|
|
if (!video_thread || !video_thread->isRunning()) {
|
|
vqueue->clear();
|
|
continue;
|
|
}
|
|
/*
|
|
#if (FORCE_BREAK_EOF)
|
|
// ! 종료 패킷생성 처리함.. (forceEOF, end 확인해서 PUT 하지 않도록 해야함)
|
|
if(pkt.isEOF() == true && forceEOF == false && end == false)
|
|
{
|
|
// if(playerID == 1)
|
|
// {
|
|
// qInfo() << "V-CEOF:2" << __FUNCTION__ << __LINE__;
|
|
// }
|
|
vqueue->blockFull(true);
|
|
Packet quit_pkt(Packet::createEOF());
|
|
quit_pkt.position = 0; //?
|
|
vqueue->put(quit_pkt);
|
|
if(aqueue != NULL && forceEOF == false && end == false)
|
|
{
|
|
Packet aquit_pkt(Packet::createEOF());
|
|
aquit_pkt.position = 0;
|
|
aqueue->put(aquit_pkt);
|
|
}
|
|
forceEOF = true; // 정상 종료 메시지
|
|
continue;
|
|
}
|
|
// PUT 확인
|
|
if(end == true) {
|
|
break;
|
|
}
|
|
#endif
|
|
*/
|
|
vqueue->blockFull(!audio_thread || !audio_thread->isRunning() || !aqueue || aqueue->isEnough());
|
|
vqueue->put(pkt); //affect audio_thread
|
|
last_vpts = pkt.pts;
|
|
// 5FPS 10초 영상의 경우 50 프레임이며 최종 PTS 는 9.8(+0.2 Duration)임
|
|
PLAYER_DEBUG_V2("@@@PACKET_ADD:",100,pkt.pts,pkt.dts);
|
|
}
|
|
}
|
|
#if !(DO_NOT_USE_SUBTITLE)
|
|
else if (demuxer->subtitleStreams().contains(stream)) { //subtitle
|
|
Q_EMIT internalSubtitlePacketRead(demuxer->subtitleStreams().indexOf(stream), pkt);
|
|
}
|
|
#endif
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
// 정지된 경우 여기까지 오지 않음
|
|
PLAYER_DEBUG("@@@DEMUX_THREAD_DONE",100);
|
|
//qInfo() << "EXIT" << playerID << "DEMUX(1):" << __FUNCTION__ << __LINE__;// << " time:" << tr.elapsed();
|
|
#endif //
|
|
|
|
#if (FORCE_BREAK_EOF_LOG)
|
|
QElapsedTimer ql;
|
|
ql.start();
|
|
qInfo() << __FUNCTION__ << __LINE__ << "DONE" << playerID;
|
|
#endif
|
|
|
|
m_buffering = false;
|
|
m_buffer = 0;
|
|
while (audio_thread && audio_thread->isRunning())
|
|
{
|
|
#if (FORCE_BREAK_EOF)
|
|
audio_thread->forceEnd = true;
|
|
#endif
|
|
#if !(OFF_OTHER_DEBUG)
|
|
qDebug("waiting audio thread.......");
|
|
#endif
|
|
Packet quit_pkt(Packet::createEOF());
|
|
quit_pkt.position = 0;
|
|
aqueue->put(quit_pkt);
|
|
aqueue->blockEmpty(false); //FIXME: why need this
|
|
audio_thread->pause(false);
|
|
audio_thread->wait(500);
|
|
}
|
|
#if (FORCE_BREAK_EOF_LOG)
|
|
qInfo() << __FUNCTION__ << __LINE__ << "A THREAD DONE" << playerID;
|
|
#endif
|
|
while (video_thread && video_thread->isRunning())
|
|
{
|
|
#if (FORCE_BREAK_EOF)
|
|
video_thread->forceEnd = true;
|
|
#endif
|
|
|
|
#if !(OFF_OTHER_DEBUG)
|
|
qDebug("waiting video thread.......");
|
|
#endif
|
|
Packet quit_pkt(Packet::createEOF());
|
|
quit_pkt.position = 0;
|
|
vqueue->put(quit_pkt);
|
|
vqueue->blockEmpty(false);
|
|
video_thread->pause(false);
|
|
video_thread->wait(500);
|
|
}
|
|
#if (PLAY_SYNC_FIX2)
|
|
thread->disconnect(this, SIGNAL(seekFinished(qint64,qint64)));
|
|
#else
|
|
thread->disconnect(this, SIGNAL(seekFinished(qint64)));
|
|
#endif
|
|
#if !(OFF_OTHER_DEBUG)
|
|
qDebug("Demux thread stops running....");
|
|
#endif
|
|
#if (FORCE_BREAK_EOF_LOG)
|
|
qInfo() << __FUNCTION__ << __LINE__ << "V THREAD DONE" << playerID << ql.elapsed();
|
|
#endif
|
|
|
|
// qInfo() << "EXIT" << playerID << "DEMUX(2):" << __FUNCTION__ << __LINE__;
|
|
|
|
|
|
if (demuxer->atEnd())
|
|
{
|
|
Q_EMIT mediaStatusChanged(FAV::EndOfMedia);
|
|
}
|
|
else
|
|
{
|
|
#if (FORCE_END_OF_MEDIA)
|
|
Q_EMIT mediaStatusChanged(FAV::EndOfMedia);
|
|
#else
|
|
Q_EMIT mediaStatusChanged(FAV::StalledMedia);
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
bool AVDemuxThread::tryPause(unsigned long timeout)
|
|
{
|
|
if (!paused)
|
|
return false;
|
|
QMutexLocker lock(&buffer_mutex);
|
|
Q_UNUSED(lock);
|
|
cond.wait(&buffer_mutex, timeout);
|
|
return true;
|
|
}
|
|
} //namespace FAV
|