Files
fmviewer3/project/fm_viewer/fav/AVDemuxThread.cpp
2026-02-21 17:11:31 +09:00

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