1928 lines
66 KiB
C++
1928 lines
66 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 "VideoThread.h"
|
|
#include "AVThread_p.h"
|
|
#include "Packet.h"
|
|
#include "AVClock.h"
|
|
#include "VideoCapture.h"
|
|
#include "VideoDecoder.h"
|
|
#include "VideoRenderer.h"
|
|
#include "Statistics.h"
|
|
#include "Filter.h"
|
|
#include "FilterContext.h"
|
|
#include "OutputSet.h"
|
|
#include "AVCompat.h"
|
|
#include <QtCore/QFileInfo>
|
|
#include "Logger.h"
|
|
|
|
#include "fav_common.h"
|
|
#include "fav_profile.h"
|
|
#include <QElapsedTimer>
|
|
|
|
namespace FAV {
|
|
|
|
class VideoThreadPrivate : public AVThreadPrivate
|
|
{
|
|
public:
|
|
VideoThreadPrivate():
|
|
AVThreadPrivate()
|
|
, force_fps(0)
|
|
, force_dt(0)
|
|
, capture(0)
|
|
, filter_context(0)
|
|
{
|
|
}
|
|
~VideoThreadPrivate() {
|
|
//not neccesary context is managed by filters.
|
|
if (filter_context) {
|
|
delete filter_context;
|
|
filter_context = 0;
|
|
}
|
|
}
|
|
|
|
VideoFrameConverter conv;
|
|
qreal force_fps; // <=0: try to use pts. if no pts in stream(guessed by 5 packets), use |force_fps|
|
|
// not const.
|
|
int force_dt; //unit: ms. force_fps = 1/force_dt.
|
|
|
|
double pts; //current decoded pts. for capture. TODO: remove
|
|
VideoCapture *capture;
|
|
VideoFilterContext *filter_context;//TODO: use own smart ptr. QSharedPointer "=" is ugly
|
|
VideoFrame displayed_frame;
|
|
|
|
};
|
|
|
|
VideoThread::VideoThread(QObject *parent) :
|
|
AVThread(*new VideoThreadPrivate(), parent)
|
|
{
|
|
#if (USE_SKIP_COUNT)
|
|
nSkip = 0;
|
|
#if (FIXED_FPS_DURATION)
|
|
stopPosition = -1;
|
|
#endif
|
|
#endif
|
|
do_not_update_next_clock = false;
|
|
}
|
|
|
|
//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;
|
|
if (old)
|
|
disconnect(old, SIGNAL(requested()), this, SLOT(addCaptureTask()));
|
|
if (cap)
|
|
connect(cap, SIGNAL(requested()), this, SLOT(addCaptureTask()));
|
|
if (cap->autoSave() && cap->name.isEmpty()) {
|
|
// statistics is already set by AVPlayer
|
|
cap->setCaptureName(QFileInfo(d.statistics->url).completeBaseName());
|
|
}
|
|
return old;
|
|
}
|
|
|
|
VideoCapture* VideoThread::videoCapture() const
|
|
{
|
|
return d_func().capture;
|
|
}
|
|
|
|
void VideoThread::addCaptureTask()
|
|
{
|
|
if (!isRunning())
|
|
return;
|
|
class CaptureTask : public QRunnable {
|
|
public:
|
|
CaptureTask(VideoThread *vt) : vthread(vt) {}
|
|
void run() {
|
|
VideoCapture *vc = vthread->videoCapture();
|
|
if (!vc)
|
|
return;
|
|
VideoFrame frame(vthread->displayedFrame());
|
|
//vthread->applyFilters(frame);
|
|
vc->setVideoFrame(frame);
|
|
vc->start();
|
|
}
|
|
private:
|
|
VideoThread *vthread;
|
|
};
|
|
scheduleTask(new CaptureTask(this));
|
|
}
|
|
|
|
void VideoThread::clearRenderers()
|
|
{
|
|
d_func().outputSet->sendVideoFrame(VideoFrame());
|
|
}
|
|
|
|
VideoFrame VideoThread::displayedFrame() const
|
|
{
|
|
return d_func().displayed_frame;
|
|
}
|
|
|
|
void VideoThread::setFrameRate(qreal value)
|
|
{
|
|
DPTR_D(VideoThread);
|
|
d.force_fps = value;
|
|
if (d.force_fps != 0.0) {
|
|
d.force_dt = int(1000.0/d.force_fps);
|
|
} else {
|
|
d.force_dt = 0;
|
|
}
|
|
}
|
|
|
|
void VideoThread::setBrightness(int val)
|
|
{
|
|
setEQ(val, 101, 101);
|
|
}
|
|
|
|
void VideoThread::setContrast(int val)
|
|
{
|
|
setEQ(101, val, 101);
|
|
}
|
|
|
|
void VideoThread::setSaturation(int val)
|
|
{
|
|
setEQ(101, 101, val);
|
|
}
|
|
|
|
void VideoThread::setEQ(int b, int c, int s)
|
|
{
|
|
class EQTask : public QRunnable {
|
|
public:
|
|
EQTask(VideoFrameConverter *c)
|
|
: brightness(0)
|
|
, contrast(0)
|
|
, saturation(0)
|
|
, conv(c)
|
|
{
|
|
//qDebug("EQTask tid=%p", QThread::currentThread());
|
|
}
|
|
void run() {
|
|
conv->setEq(brightness, contrast, saturation);
|
|
}
|
|
int brightness, contrast, saturation;
|
|
private:
|
|
VideoFrameConverter *conv;
|
|
};
|
|
DPTR_D(VideoThread);
|
|
EQTask *task = new EQTask(&d.conv);
|
|
task->brightness = b;
|
|
task->contrast = c;
|
|
task->saturation = s;
|
|
if (isRunning()) {
|
|
scheduleTask(task);
|
|
} else {
|
|
task->run();
|
|
delete task;
|
|
}
|
|
}
|
|
|
|
void VideoThread::applyFilters(VideoFrame &frame)
|
|
{
|
|
DPTR_D(VideoThread);
|
|
QMutexLocker locker(&d.mutex);
|
|
Q_UNUSED(locker);
|
|
if (!d.filters.isEmpty()) {
|
|
//sort filters by format. vo->defaultFormat() is the last
|
|
foreach (Filter *filter, d.filters) {
|
|
VideoFilter *vf = static_cast<VideoFilter*>(filter);
|
|
if (!vf->isEnabled())
|
|
continue;
|
|
if (vf->prepareContext(d.filter_context, d.statistics, &frame))
|
|
vf->apply(d.statistics, &frame);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// filters on vo will not change video frame, so it's safe to protect frame only in every individual vo
|
|
bool VideoThread::deliverVideoFrame(VideoFrame &frame)
|
|
{
|
|
PLAYER_DEBUG("###deliverVideoFrame start",80);
|
|
|
|
DPTR_D(VideoThread);
|
|
/*
|
|
* TODO: video renderers sorted by preferredPixelFormat() and convert in AVOutputSet.
|
|
* Convert only once for the renderers has the same preferredPixelFormat().
|
|
*/
|
|
d.outputSet->lock();
|
|
QList<AVOutput *> outputs = d.outputSet->outputs();
|
|
VideoRenderer *vo = 0;
|
|
if (!outputs.isEmpty())
|
|
vo = static_cast<VideoRenderer*>(outputs.first());
|
|
if (vo && (!vo->isSupported(frame.pixelFormat())
|
|
|| (vo->isPreferredPixelFormatForced() && vo->preferredPixelFormat() != frame.pixelFormat())
|
|
)) {
|
|
|
|
|
|
VideoFormat fmt(frame.format());
|
|
if (fmt.hasPalette() || fmt.isRGB())
|
|
fmt = VideoFormat::Format_RGB32;
|
|
else
|
|
fmt = vo->preferredPixelFormat();
|
|
VideoFrame outFrame(d.conv.convert(frame, fmt));
|
|
if (!outFrame.isValid()) {
|
|
d.outputSet->unlock();
|
|
return false;
|
|
}
|
|
frame = outFrame;
|
|
}
|
|
PLAYER_DEBUG("###deliverVideoFrame mid",80);
|
|
|
|
#if (PREVENT_OVER_DURATION_RENDER)
|
|
//if(duration >= frame.timestamp()) {
|
|
#endif // PREVENT_OVER_DURATION_RENDER
|
|
|
|
d.outputSet->sendVideoFrame(frame); //TODO: group by format, convert group by group
|
|
#if (PREVENT_OVER_DURATION_RENDER)
|
|
//}
|
|
#endif // PREVENT_OVER_DURATION_RENDER
|
|
d.outputSet->unlock();
|
|
|
|
PLAYER_DEBUG("###deliverVideoFrame end",80);
|
|
Q_EMIT frameDelivered();
|
|
return true;
|
|
}
|
|
|
|
//TODO: if output is null or dummy, the use duration to wait
|
|
#define VTHREAD_DEBUG 0
|
|
#define VDI qInfo()
|
|
// qInfo() << __FUNCTION__ << __LINE__;
|
|
//#define CHECK_POS_P1 if(playerID == 0) { qInfo() << __FUNCTION__ << __LINE__ << " stop:" << stopPosition; }
|
|
//int g_invalid_packet_count = 0;
|
|
|
|
//#undef PLAY_SYNC_FIX2
|
|
|
|
#if (PLAY_SYNC_FIX2)
|
|
// 2024/02/07 수정현황
|
|
// - PAUSED 된 상태에서 화면끝쪽으로 이동하면 SEEK 가 종료되지 않는다
|
|
// - " PAUSED 가 풀리는 경우가 있다
|
|
void VideoThread::run()
|
|
{
|
|
PLAYER_DEBUG("###VIDEO_THREAD_START",100);
|
|
|
|
DPTR_D(VideoThread);
|
|
// 디코더가 존재하지 않거나 ouput 이 없을 경우 취소
|
|
if (!d.dec || !d.dec->isAvailable() || !d.outputSet)
|
|
return;
|
|
resetState(); // AVThread 초기화
|
|
|
|
|
|
// 캡쳐 자동 저장 지정되어 있을 경우 파일명 지정
|
|
if (d.capture->autoSave()) {
|
|
d.capture->setCaptureName(QFileInfo(d.statistics->url).completeBaseName());
|
|
}
|
|
|
|
// 필터 지정
|
|
//not neccesary context is managed by filters.
|
|
d.filter_context = VideoFilterContext::create(VideoFilterContext::QtPainter);
|
|
|
|
// 디코더 지정
|
|
VideoDecoder *dec = static_cast<VideoDecoder*>(d.dec);
|
|
|
|
Packet pkt; // 패킷 변수 할당 (send packet, receive frame 은 동일한 thread 에서 처리되어야 함)
|
|
QVariantHash *dec_opt = &d.dec_opt_normal; //TODO: restore old framedrop option after seek
|
|
|
|
// SEEK 가 시작될 경우 KEY_FRAME 부터 SEND 해야함???
|
|
bool wait_key_frame = false;
|
|
int nb_dec_slow = 0;
|
|
int nb_dec_fast = 0;
|
|
|
|
// 최초의 SEEK 시에는 프레임 드랍 발생하지 않는다??
|
|
qint32 seek_count = 0; // wm4 says: 1st seek can not use frame drop for decoder
|
|
|
|
//
|
|
const int kNbSlowSkip = 20;
|
|
|
|
// N 프레임 이상 느려질 경우 프레임 디코딩 처리하지 않도록 한다? (ffmpeg 디코더만 가능?)
|
|
const int kNbSlowFrameDrop = 10;
|
|
|
|
// 기본적으로 EXTERNAL CLOCK 을 사용하니 제거
|
|
//bool sync_audio = d.clock->clockType() == AVClock::AudioClock;
|
|
//bool sync_video = d.clock->clockType() == AVClock::VideoClock; // no frame drop
|
|
// qInfo() << sync_audio << sync_video << __FUNCTION__;
|
|
//const qint64 start_time = QDateTime::currentMSecsSinceEpoch();
|
|
qreal v_a = 0;
|
|
//int nb_no_pts = 0;
|
|
//bool wait_audio_drain
|
|
//const char* pkt_data = NULL; // workaround for libav9 decode fail but error code >= 0
|
|
//qint64 last_deliver_time = 0;
|
|
int sync_id = 0;
|
|
|
|
#if (USE_SKIP_COUNT)
|
|
// 최소 1프레임은 그리기..
|
|
bool drawFirstFrame = false;
|
|
#endif
|
|
|
|
#if (FIRST_FRAME_NOTIFY)
|
|
bFirstFrameDraw = false;
|
|
#endif
|
|
|
|
#if (FORCE_BREAK_EOF)
|
|
forceEnd = false;
|
|
aboutToEnd = false;
|
|
#endif
|
|
|
|
// QElapsedTimer et;
|
|
// et.start();
|
|
|
|
|
|
|
|
//qInfo() << __FUNCTION__ << "VTHREAD START:----------------------------------" << playerID;
|
|
|
|
// STOP DTS
|
|
//qreal stop_dts = ((qreal)stopPosition) / 1000000.0;
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
double dts_offset = 0.0;
|
|
int frame_offset = 0;
|
|
int negative_frame_count = 0; // timestamp -1 이 발생한 회수
|
|
endStop = false;
|
|
#endif // FIX_PLAYER_END_CLIP
|
|
|
|
bool no_more_packet = false;
|
|
while (!d.stop) // 재생시 무한 반복
|
|
{
|
|
//qInfo() << "###CLOCK:" << d.clock->value() << __FUNCTION__ << __LINE__;
|
|
|
|
#if (FORCE_BREAK_EOF)
|
|
if(forceEnd)
|
|
{
|
|
PLAYER_DEBUG("###BREAK_FORCE_END",60);
|
|
break;
|
|
}
|
|
#endif
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
if(endStop)
|
|
{
|
|
PLAYER_DEBUG("###BREAK_END_STOP",60);
|
|
break;
|
|
}
|
|
#endif
|
|
processNextTask();
|
|
// SEEK 가 아닌 상태에서만 PAUSE 처리
|
|
if (d.render_pts0 < 0) {
|
|
// 여기서 멈춤
|
|
//VALUE_DEBUG("RENDER tryPause:",tryPause());
|
|
// 사용자 PAUSE 처리하면 tryPause() 는 true 로 리턴됨...
|
|
if (tryPause()) { //DO NOT continue, or stepForward() will fail
|
|
PLAYER_DEBUG("###TRY_PAUSE_TRUE",1);
|
|
}
|
|
else
|
|
{
|
|
if (isPaused())
|
|
{
|
|
PLAYER_DEBUG("###CONTINUE_PAUSED....",1);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
// SEEK 요청된 상태일 경우 false 처리하고 INVALID PACKET 으로 변경???
|
|
if (d.seek_requested)
|
|
{
|
|
d.seek_requested = false;
|
|
pkt = Packet(); // 최종 디코딩 실패 + 패킷은 정상 = SEEK 중일 경우 패킷을 초기화
|
|
//msleep(1);
|
|
}
|
|
else
|
|
{
|
|
// d.render_pts0 < 0 means seek finished here
|
|
if (d.clock->syncId() > 0) {
|
|
// d.render_pts0 < 0 은 SEEK 가 종료되었음..
|
|
if (d.render_pts0 < 0 && sync_id > 0 && aboutToEnd == false)
|
|
{
|
|
//msleep(10); // 10
|
|
v_a = 0;
|
|
// SEEK 를 앞으로 당기면 너무 많이 발생하나 실제 처리시간은 0.046SEC
|
|
PLAYER_DEBUG("###CONTINUE_RENDER_PTS0<0",1);
|
|
continue;
|
|
}
|
|
} else {
|
|
sync_id = 0;
|
|
}
|
|
}
|
|
// EOF 접수된 상태에서는 뒤로 SEEK 할 수 없음
|
|
if(!pkt.isValid() && !pkt.isEOF() && !no_more_packet) {
|
|
// 처리하지 않으면 시간 오래 걸림
|
|
// AVDemuxThread 에서 종료할 경우 take 에 최대 1초까지 걸림...
|
|
// SEEK 요청 위치가 최종 PTS 이상일 경우에도 최대 1초까지 걸림
|
|
pkt = d.packets.take(); //wait to dequeue
|
|
PLAYER_DEBUG_V("###PACKET_TAKE",1,pkt.pts);
|
|
}
|
|
// 최종 패킷을 수신했으면 no_more_packet = TRUE
|
|
// 후반부로 이동하면 문제가됨 -> SEEK 중(d.render_pts0 > 0) 에서는 처리하지 않도록 하면 수정됨 -> 안됨
|
|
// 종료를 EOF PACKET 이 아닌 EOF FRAME 으로 처리하고
|
|
// EOF 프레임이 발생했을때만 BREAK 하도록 수정하는 것이 바람직함..
|
|
// DEMUX THREAD 에서는 종료하지 않도록 처리함
|
|
#if !(PLAY_BREAK_EOF_FRAME)
|
|
if (pkt.isEOF() && no_more_packet == false && d.render_pts0 < 0)
|
|
{
|
|
wait_key_frame = false;
|
|
no_more_packet = true;
|
|
PLAYER_DEBUG_V("###LAST_PACKET_RECEIVED R/PTS:",50,d.render_pts0);
|
|
}
|
|
else
|
|
#endif // PLAY_BREAK_EOF_FRAME
|
|
{
|
|
// AVDemuxThread::seekInternal 에서
|
|
// 빈 PACKET 을 생성하여 pkt.pts 로 위치만 전달하여 SEEK 상태가 됨
|
|
// 실제 영상 길이가 짧을 경우 SEEK 시 readframe 처리하면 처음부터 다시 read 됨
|
|
// 30FPS 2분 영상의 경우 83.094 초 SEEK 요구시 75 초 부터 READ FRAME 발생함...
|
|
if (pkt.isSeek()) // !pkt.isValid()
|
|
{
|
|
// SEEK INTERNAL 시 d.packets.size 는 1 이 되어야 하는데 18임...
|
|
// 확인해보면 AVDemuxThread 에서 즉시 패킷을 최대로 추가하고
|
|
// VideoThread 에서 SEND 처리해버림
|
|
PLAYER_DEBUG_V("###SEEK_START_PACKET TO:",50,pkt.pts);
|
|
wait_key_frame = true;
|
|
if (d.dec == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
// AVDemuxThread::seekInternal 에서 pkt.queue 를 먼저 CLEAR 하고 있음
|
|
// FLUSH 처리하면 avcodec_flush_buffers 호출되어 다시 일정 패킷 공급해야 DECODE 처리됨
|
|
d.dec->flush();
|
|
d.render_pts0 = pkt.pts; // SEEK 요청시의 요구 위치
|
|
sync_id = pkt.position; // position 은 무슨 의미?
|
|
d.pts_history = ring<qreal>(d.pts_history.capacity());
|
|
v_a = 0;
|
|
PLAYER_DEBUG("###SEEK_WAIT_FRAME_CONTINUE",1);
|
|
// 5FPS/10SEC 영상에서 PAUSE 후 끝까지 DRAG SEEK 하면 SEEK 종료가 안됨 msleep 추가로 해결
|
|
msleep(1);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// render_pts0 가 >= 일 경우 SEEK 요청 위치가 설정됨
|
|
bool seeking = d.render_pts0 >= 0.0;
|
|
|
|
|
|
// if (pkt.pts <= 0 && !pkt.isEOF() && pkt.data.size() > 0)
|
|
// {
|
|
// nb_no_pts++;
|
|
// }
|
|
// else
|
|
// {
|
|
// nb_no_pts = 0;
|
|
// }
|
|
|
|
|
|
// ### 동기화 파트 -> 디코딩 뒤쪽으로 이동
|
|
|
|
// 재생중 디코더 변경 가능성??
|
|
// if (dec != static_cast<VideoDecoder*>(d.dec))
|
|
// {
|
|
// dec = static_cast<VideoDecoder*>(d.dec);
|
|
// if (!pkt.hasKeyFrame)
|
|
// {
|
|
// wait_key_frame = true;
|
|
// v_a = 0;
|
|
// continue;
|
|
// }
|
|
// }
|
|
|
|
// H265 stream 변경시 발생가능???
|
|
if (dec == NULL || d.dec == NULL)
|
|
{
|
|
PLAYER_DEBUG("###DES IS NULL",100);
|
|
continue;
|
|
}
|
|
// 디코딩 프레임 드랍 옵션 변경시 재적용
|
|
// if (dec_opt != dec_opt_old)
|
|
// {
|
|
// dec->setOptions(*dec_opt);
|
|
// }
|
|
|
|
// 디코딩
|
|
|
|
|
|
if(pkt.isValid()) {
|
|
// SEEK 중에는 KEYFRAME 이 아니면 SEND 하지 않고 SKIP 처리함
|
|
if(wait_key_frame) {
|
|
if(!pkt.hasKeyFrame) {
|
|
PLAYER_DEBUG("###KEY_FRAME_SKIP_SEND",100);
|
|
continue;
|
|
}
|
|
wait_key_frame = false;
|
|
}
|
|
bool sended = dec->send(&pkt);
|
|
PLAYER_DEBUG_V2("###SEND_PACKET",1,sended,pkt.pts);
|
|
} else {
|
|
// SEEK 중에는 SEND 하지 않아야 하는 것 아님???
|
|
// 빈 패킷이라도 전달해야 마지막 프레임까지 추출됨
|
|
// 5FPS 10초 영상을 정지상태에서 끝까지 SEEK 하면 반복됨
|
|
bool sended = dec->send(NULL);
|
|
PLAYER_DEBUG_V2("###SEND_NULL_PACKET CLOCK:",1,sended,d.clock->value());
|
|
}
|
|
|
|
// decode 로 처리하지 말고 sendframe, receiveframe 으로 처리해야함!!!
|
|
// 더이상의 패킷이 없을 경우에만 종료
|
|
if (!dec->receive())
|
|
{
|
|
// packet buffer 가 빈 상태에서는 receive faile 발생함
|
|
// 실패시 처리
|
|
d.pts_history.push_back(d.pts_history.back());
|
|
//qWarning("Decode video failed. undecoded: %d/%d", dec->undecodedSize(), pkt.data.size());
|
|
|
|
// SEEK 와 관련이 있는지 모르겠음..
|
|
/*
|
|
if (pkt.isEOF() && drawFirstFrame == true)
|
|
{
|
|
Q_EMIT eofDecoded();
|
|
if (d.render_pts0 >= 0) {
|
|
d.render_pts0 = -1;
|
|
d.clock->syncEndOnce(sync_id);
|
|
if (seek_count == -1)
|
|
seek_count = 1;
|
|
else if (seek_count > 0)
|
|
seek_count++;
|
|
}
|
|
if (!pkt.position) // == 0?
|
|
{
|
|
PLAYER_DEBUG("###BREAK_RECEIVE_FAIL+EOF",100);
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
// INVALID PACKET 생성후 CONTINUE
|
|
pkt = Packet();
|
|
v_a = 0; //?
|
|
PLAYER_DEBUG("###FRAME_RECEIVE_FAILED CONTINUE",1);
|
|
if(no_more_packet) {
|
|
PLAYER_DEBUG("###BREAK_NO_MORE_PACKET",60);
|
|
//break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// 초기 패킷이 디코딩 되지 않으면
|
|
// VDI << "pkt.pts:" << pkt.pts << " dst:" << pkt.dts << " duration:" << pkt.duration;
|
|
|
|
// reduce here to ensure to decode the rest data in the next loop
|
|
// 다음 데이터로 이동
|
|
|
|
// 필요없을것!!!
|
|
if (!pkt.isEOF())
|
|
{
|
|
pkt.skip(pkt.data.size() - dec->undecodedSize());
|
|
}
|
|
|
|
VideoFrame frame = dec->frame();
|
|
// 디코딩 처리 후 동기화
|
|
PLAYER_DEBUG_V("###RECEIVE_FRAME: PTS:",100,frame.pts());
|
|
if (!frame.isValid())
|
|
{
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
if(drawFirstFrame == false) {
|
|
frame_offset++;
|
|
}
|
|
#endif // #if (FIX_PLAYER_END_CLIP)
|
|
|
|
// if (pkt_data == pkt.data.constData()) //FIXME: for libav9. what about other versions?
|
|
// {
|
|
// pkt = Packet();
|
|
// }
|
|
// else
|
|
// {
|
|
// pkt_data = pkt.data.constData();
|
|
// }
|
|
v_a = 0; //?
|
|
PLAYER_DEBUG("###CONTINUE_INVALID_FRAME",1);
|
|
continue;
|
|
} // 비정상 프레임
|
|
|
|
// ### 2동기화 처리 시작
|
|
const qreal dts = frame.pts();
|
|
const qreal duration = frame.duration(); // SEEK 확인을 위해서는 pts+duration 필요
|
|
// DTS - 현재시간 = > 0 : WAIT, < 0 : SKIP
|
|
qreal diff = dts > 0 ? dts - d.clock->value() + v_a : v_a;
|
|
|
|
|
|
PLAYER_DEBUG_V3("###DTS/CLOCK/DIFFER:",1,dts,d.clock->value(),diff);
|
|
|
|
/*
|
|
if (pkt.isEOF())
|
|
{
|
|
#if (FORCE_BREAK_EOF)
|
|
diff = 0;
|
|
#else
|
|
diff = qMin<qreal>(1.0, qMax<qreal>(d.delay, 1.0/d.statistics->video_only.currentDisplayFPS()));
|
|
#endif
|
|
}
|
|
*/
|
|
|
|
if (diff > kSyncThreshold)
|
|
{
|
|
nb_dec_fast++;
|
|
}
|
|
else
|
|
{
|
|
nb_dec_fast /= 2;
|
|
}
|
|
if (seeking)
|
|
{
|
|
nb_dec_slow = 0;
|
|
nb_dec_fast = 0;
|
|
}
|
|
// 현재 오차가 클 경우??
|
|
if (d.delay < -0.5 && d.delay > diff)
|
|
{
|
|
if (!seeking)
|
|
{
|
|
// ensure video will not later than 2s
|
|
if (diff < -(SLOW_SKIP_SEC) || (nb_dec_slow > kNbSlowSkip && diff < -1.0 && !pkt.hasKeyFrame))
|
|
{
|
|
nb_dec_slow = 0;
|
|
wait_key_frame = true;
|
|
pkt = Packet();
|
|
v_a = 0;
|
|
PLAYER_DEBUG("###CONTINUE_DROP_FRAME",100);
|
|
continue; // 배속설정하면 32배속에서 .. 반복됨
|
|
}
|
|
else
|
|
{
|
|
nb_dec_slow++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nb_dec_slow >= kNbSlowFrameDrop)
|
|
{
|
|
nb_dec_slow = qMax(0, nb_dec_slow-1); // nb_dec_slow < kNbSlowFrameDrop will reset decoder frame drop flag
|
|
}
|
|
}
|
|
// can not change d.delay after! we need it to comapre to next loop
|
|
d.delay = diff;
|
|
if (seeking)
|
|
{
|
|
diff = 0; // TODO: here?
|
|
}
|
|
// 동기화가 되어 있지 않으면 여기시 동기화 처리함-> clock 을 잘못 설정하면 큰 값이 반영되어 대기시간이 몇초까지 늘어남
|
|
if (diff > 0)
|
|
{
|
|
// wait to dts reaches
|
|
// d.force_fps>0: wait after decoded before deliver
|
|
if (d.force_fps <= 0 && drawFirstFrame == true)
|
|
{
|
|
// 플레이어 종료되지 않고 대기함..
|
|
// TODO: count decoding and filter time, or decode immediately but wait for display
|
|
if(forceEnd)
|
|
{
|
|
PLAYER_DEBUG("###BREAK_FORCE_END",100);
|
|
break;
|
|
}
|
|
// 여기서 WAIT 되는 항목이 실제 .. 임
|
|
PLAYER_DEBUG_V3("###WAIT1",100,diff*1000UL,dts,d.clock->value());
|
|
waitAndCheck(diff*1000UL, dts);
|
|
diff = 0;
|
|
}
|
|
}
|
|
|
|
// clock update 가능함
|
|
do_not_update_next_clock = false;
|
|
|
|
// update here after wait. TODO: use decoded timestamp/guessed next pts?
|
|
#if (FIX_SYNC_TIME)
|
|
if(!seeking) // 추가 탐색시에는 시간 업데이트 하지 않는다.
|
|
#endif
|
|
{
|
|
// 여기서 clock update 을 업데이트 하니 문제가 됨
|
|
//qInfo() << "video time:" << dts;
|
|
// PAUSE 버벅 문제는 여기서 발생하는 문제가 아님
|
|
d.clock->updateVideoTime(dts); // FIXME: dts or pts? // 실제 여기서 잘못된 position 이 발생함????
|
|
//VALUE_DEBUG("UPDATE VIDEO TIME",dts);
|
|
|
|
}
|
|
|
|
bool skip_render = false;
|
|
if (qAbs(diff) < 0.5)
|
|
{
|
|
if (diff < -kSyncThreshold)
|
|
{ //Speed up. drop frame?
|
|
#if (SKIP_FIRST_CORRUPT_FRAME)
|
|
continue;
|
|
#endif
|
|
}
|
|
}
|
|
else if (!seeking) //when to drop off?
|
|
{
|
|
if (diff < 0)
|
|
{
|
|
if (nb_dec_slow > kNbSlowSkip)
|
|
{
|
|
skip_render = !pkt.hasKeyFrame && (nb_dec_slow %2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const double s = qMin<qreal>(0.01*(nb_dec_fast>>1), diff);
|
|
PLAYER_DEBUG_V3("###WAIT2",100,diff*1000UL,dts,d.clock->value());
|
|
waitAndCheck(s*1000UL, dts);
|
|
diff = 0;
|
|
skip_render = false;
|
|
}
|
|
}
|
|
#if (USE_SKIP_COUNT)
|
|
if(drawFirstFrame == true && g_SkipCount != 0) // 최소 1회 render 되었으며 skip_count 가 설정되었을 경우
|
|
{
|
|
nSkip = (nSkip + 1) % g_SkipCount; // x 회당 1회 skip 처리함..
|
|
if(skip_render == false && seeking == false)
|
|
{
|
|
skip_render = (nSkip != 0);
|
|
}
|
|
}
|
|
#endif // USE_SKIP_COUNT
|
|
|
|
//audio packet not cleaned up?
|
|
if (diff > 0 && diff < 1.0 && !seeking)
|
|
{
|
|
// PAUSE/RESUME 후에 300ms 정도 발생하며 멈칫 함 -> 사운드 문제 아님
|
|
// clock 이 PAUSE 이전 시간으로 업데이트 되어 있음
|
|
// RMPlayer::sync_clock() 에서 updateExternalClock 호출 할 경우 CLOCK 이 재생시간 이전으로 잠깐 돌아가는
|
|
// 문제가 발생하여 딜레이가 생김
|
|
// @TODO https://stackoverflow.com/questions/53449946/ffmpeg-stream-decoding-artefacts-when-not-using-ffplay
|
|
PLAYER_DEBUG_V2("###WAIT3_MS/DTS_START:",100,diff*1000UL,dts);
|
|
if(diff*1000UL > 100) {
|
|
PLAYER_DEBUG_V2("###WAIT3_END DIFF/CLOCK:",100,diff,d.clock->value());
|
|
}
|
|
waitAndCheck(diff*1000UL, dts);
|
|
diff = 0;
|
|
//qInfo() << "WAIT>>>>" << diff << __FUNCTION__ << __LINE__;
|
|
}
|
|
// SEND PACKET 에서 CONTINUE 처리함
|
|
/*
|
|
if (wait_key_frame)
|
|
{
|
|
if (!pkt.hasKeyFrame)
|
|
{
|
|
pkt = Packet();
|
|
v_a = 0;
|
|
// SEEK 중 wait_key_frame 걸려서 SEEKING 까지 도달하지 못함!!!
|
|
PLAYER_DEBUG("###CONTINUE_WAIT_KEY_FRAME",100);
|
|
continue;
|
|
}
|
|
wait_key_frame = false;
|
|
} */
|
|
|
|
// MAYBE not seeking. We should not drop the frames near the seek target. FIXME: use packet pts distance instead of -0.05 (20fps)
|
|
//QVariantHash *dec_opt_old = dec_opt;
|
|
// SEEK 상태가 아니거나 SEEK 위치(target)까지 가까운 경우, 프레임 드랍을 하지 않도록 처리
|
|
if (!seeking || pkt.pts - d.render_pts0 >= -0.05)
|
|
{
|
|
if (nb_dec_slow < kNbSlowFrameDrop)
|
|
{
|
|
if (dec_opt == &d.dec_opt_framedrop)
|
|
{
|
|
dec_opt = &d.dec_opt_normal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dec_opt == &d.dec_opt_normal)
|
|
{
|
|
dec_opt = &d.dec_opt_framedrop;
|
|
}
|
|
}
|
|
}
|
|
else // SEEK 상태이며 SEEK TARGET 까지 멀 경우
|
|
{
|
|
// DEMUX THREAD 에서 SEEK 시작시 drop_frame_seek 가 TRUE 가 됨
|
|
if (seek_count > 0 && d.drop_frame_seek) {
|
|
if (dec_opt == &d.dec_opt_normal) {
|
|
dec_opt = &d.dec_opt_framedrop;
|
|
}
|
|
} else {
|
|
seek_count = -1;
|
|
}
|
|
}
|
|
// ### 2동기화 처리 완료
|
|
//pkt_data = pkt.data.constData();
|
|
|
|
const qreal pts = dts;//frame.timestamp();
|
|
|
|
// SEEK 처리 완료
|
|
d.pts_history.push_back(pts);
|
|
// seek finished because we can ensure no packet before seek decoded when render_pts0 is set
|
|
//qDebug("pts0: %f, pts: %f, clock: %d", d.render_pts0, pts, d.clock->clockType());
|
|
// SEEK 요청 상태일 경우 -> SEEK_START_PACKET 3초 이후 호출됨? 이유는?
|
|
// 5FPS 10초 영상의 경우 9.623 로 SEEK 처리시 리턴되는 값음 9.6 으로 영원히 호출불가함?? 9.8로 호출됨
|
|
if (d.render_pts0 >= 0.0)
|
|
{
|
|
PLAYER_DEBUG_V3("###SEEKING....CURRENT/TARGET/EOF",100,pts,d.render_pts0,EOFPacketPTS);
|
|
// SEEK TARGET 에 도달하지 못한 경우 CONTINUE
|
|
// 마지막 프레임의 재생시간이 부정확한 경우 render_pts0(seek pos)에 PTS 도달 불가능할 수 있음
|
|
// 이 경우 유사한 경우 SEEK 종료한다.
|
|
if (pts < qMin(d.render_pts0,EOFPacketPTS)) //
|
|
{
|
|
// SEEK 상태에서 PACKET 생성해도 TAKE 처리되어 의미가 없음
|
|
if (!pkt.isEOF())
|
|
{
|
|
pkt = Packet();
|
|
}
|
|
v_a = 0;
|
|
// DRAWING 처리하지 못하고 지
|
|
// RENDER PTS: 4.531 PTS: 3.756 FAV::VideoThread::run 984
|
|
// 5FPS 10초 영상의 마지막까지 SEEK 하지 못하고 반복
|
|
PLAYER_DEBUG_V2("###CONTINUE_RENDER_PTS0_>_0 PTS/SEEK_POS:",100,pts,d.render_pts0);
|
|
continue;
|
|
}
|
|
// TARGET 에 도달한 경우(SEEK 종료)
|
|
// frame.time == seek.time (render_pts)
|
|
// 이미 EOF 패킷은 버퍼에 들어와 있음??
|
|
#if (PLAY_SYNC_FIX2)
|
|
qreal requested = d.render_pts0;
|
|
#endif // PLAY_SYNC_FIX2
|
|
d.render_pts0 = -1;
|
|
d.clock->syncEndOnce(sync_id);
|
|
d.clock->updateExternalClock(pts*1000.0); // external clock
|
|
PLAYER_DEBUG_V("###UPDATE_EXTERNAL_CLOCK",100,(pts)*1000.0);
|
|
do_not_update_next_clock = true; // 처리 완료시까지 clock update 금지
|
|
|
|
// FPS 5 * 10 SEC 영상의 경우
|
|
// 슬라이더에서 TARGET 1.415SEC 으로 SEEK 요청시 1.6SEC 으로 리턴됨
|
|
// 요청된 위치도 같이 전달하여 슬라이드 포지션은 요청된 위치로 반환하도록 처리
|
|
#if (PLAY_SYNC_FIX2)
|
|
Q_EMIT seekFinished(qint64((pts)*1000.0),qint64(requested*1000.0));
|
|
#else
|
|
Q_EMIT seekFinished(qint64(pts*1000.0));
|
|
#endif
|
|
PLAYER_DEBUG_V3("###SEEK_FINISHED PTS/REQ/EOF:",100,pts,requested,EOFPacketPTS);
|
|
|
|
if (seek_count == -1) {
|
|
seek_count = 1;
|
|
}
|
|
else if (seek_count > 0) {
|
|
seek_count++;
|
|
}
|
|
}
|
|
|
|
#if (USE_SKIP_COUNT)
|
|
drawFirstFrame = true;
|
|
#endif
|
|
// 통계 별도로 사용
|
|
// Q_ASSERT(d.statistics);
|
|
// d.statistics->video.current_time = QTime(0, 0, 0).addMSecs(int(pts * 1000.0)); //TODO: is it expensive?
|
|
applyFilters(frame);
|
|
|
|
//while can pause, processNextTask, not call outset.puase which is deperecated
|
|
// 없어도 되나???
|
|
/*
|
|
while (d.outputSet->canPauseThread())
|
|
{
|
|
d.outputSet->pauseThread(100);
|
|
processNextTask();
|
|
}*/
|
|
|
|
// 프레임 그리기
|
|
if (!deliverVideoFrame(frame))
|
|
{
|
|
PLAYER_DEBUG("###DELIVER_FRAME_FAILED",100);
|
|
continue;
|
|
}
|
|
//qInfo() << "deliverVideoFrame done" << frame.pts() << __FUNCTION__;
|
|
PLAYER_DEBUG_V3("###DRAW_FRAME_PTS/DURATION/CLOCK:",100,frame.pts(),duration,clock()->value());
|
|
|
|
#if (FIRST_FRAME_NOTIFY)
|
|
// 최초 프레임 그려진 시점에서 전송
|
|
if(bFirstFrameDraw == false)
|
|
{
|
|
bFirstFrameDraw = true;
|
|
//qInfo() << "firstFrameNotify:" << __FUNCTION__;
|
|
emit firstFrameNotify(pts);
|
|
}
|
|
#endif // FIRST_FRAME_NOTIFY
|
|
|
|
d.displayed_frame = frame;
|
|
}
|
|
|
|
PLAYER_DEBUG("###VIDEO_THREAD_DONE",100);
|
|
d.packets.clear();
|
|
}
|
|
#else // PLAY_SYNC_FIX2
|
|
#define VTHREAD_CONTINUE continue
|
|
//#define VTHREAD_CONTINUE qInfo() << "CONTINUE" << playerID << __LINE__ << __FUNCTION__; continue;
|
|
void VideoThread::run()
|
|
{
|
|
#if (PROFILE_BUILD)
|
|
FAVProfile::elapsed("Video Thread:" + QString::number(playerID) + " Started " + QString(__FUNCTION__));
|
|
#endif // PROFILE_BUILD
|
|
|
|
DPTR_D(VideoThread);
|
|
if (!d.dec || !d.dec->isAvailable() || !d.outputSet)
|
|
return;
|
|
resetState();
|
|
if (d.capture->autoSave()) {
|
|
d.capture->setCaptureName(QFileInfo(d.statistics->url).completeBaseName());
|
|
}
|
|
//not neccesary context is managed by filters.
|
|
d.filter_context = VideoFilterContext::create(VideoFilterContext::QtPainter);
|
|
VideoDecoder *dec = static_cast<VideoDecoder*>(d.dec);
|
|
Packet pkt;
|
|
QVariantHash *dec_opt = &d.dec_opt_normal; //TODO: restore old framedrop option after seek
|
|
/*!
|
|
* if we skip some frames(e.g. seek, drop frames to speed up), then then first frame to decode must
|
|
* be a key frame for hardware decoding. otherwise may crash
|
|
*/
|
|
#if (WAIT_KEY_FRAME)
|
|
bool wait_key_frame = true;
|
|
#else
|
|
bool wait_key_frame = false;
|
|
#endif
|
|
int nb_dec_slow = 0;
|
|
int nb_dec_fast = 0;
|
|
|
|
//g_invalid_packet_count = 0;
|
|
|
|
qint32 seek_count = 0; // wm4 says: 1st seek can not use frame drop for decoder
|
|
// TODO: kNbSlowSkip depends on video fps, ensure slow time <= 2s
|
|
/* kNbSlowSkip: if video frame slow count >= kNbSlowSkip, skip decoding all frames until next keyframe reaches.
|
|
* if slow count > kNbSlowSkip/2, skip rendering every 3 or 6 frames
|
|
*/
|
|
#if (SLOW_SKIP_SEC < 2)
|
|
const int kNbSlowSkip = 10;
|
|
// kNbSlowFrameDrop: if video frame slow count > kNbSlowFrameDrop, skip decoding nonref frames. only some of ffmpeg based decoders support it.
|
|
const int kNbSlowFrameDrop = 5;
|
|
#else
|
|
const int kNbSlowSkip = 20;
|
|
// kNbSlowFrameDrop: if video frame slow count > kNbSlowFrameDrop, skip decoding nonref frames. only some of ffmpeg based decoders support it.
|
|
const int kNbSlowFrameDrop = 10;
|
|
#endif
|
|
|
|
bool sync_audio = d.clock->clockType() == AVClock::AudioClock;
|
|
bool sync_video = d.clock->clockType() == AVClock::VideoClock; // no frame drop
|
|
const qint64 start_time = QDateTime::currentMSecsSinceEpoch();
|
|
qreal v_a = 0;
|
|
int nb_no_pts = 0;
|
|
//bool wait_audio_drain
|
|
const char* pkt_data = NULL; // workaround for libav9 decode fail but error code >= 0
|
|
qint64 last_deliver_time = 0;
|
|
int sync_id = 0;
|
|
|
|
#if (USE_SKIP_COUNT)
|
|
// 최소 1프레임은 그리기..
|
|
bool drawFirstFrame = false;
|
|
#endif
|
|
|
|
#if (FIRST_FRAME_NOTIFY)
|
|
bFirstFrameDraw = false;
|
|
#endif
|
|
|
|
#if (RM_TESTING)
|
|
int loopCount = 0;
|
|
int _c1 = 0;
|
|
int _c2 = 0;
|
|
int _c3 = 0;
|
|
int _c4 = 0;
|
|
int _c5 = 0;
|
|
|
|
int _c6 = 0;
|
|
int _c7 = 0;
|
|
int _c8 = 0;
|
|
int _c9 = 0;
|
|
int _c10 = 0;
|
|
int _feCount = -1;
|
|
int _fePount = -1;
|
|
|
|
|
|
displyedFrame = 0;
|
|
#endif
|
|
#if (FORCE_BREAK_EOF)
|
|
forceEnd = false;
|
|
aboutToEnd = false;
|
|
#endif
|
|
|
|
//QElapsedTimer et;
|
|
//et.start();
|
|
|
|
//qInfo() << __FUNCTION__ << "VTHREAD START:----------------------------------" << playerID;
|
|
|
|
// STOP DTS
|
|
//qreal stop_dts = ((qreal)stopPosition) / 1000000.0;
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
double dts_offset = 0.0;
|
|
int frame_offset = 0;
|
|
int negative_frame_count = 0; // timestamp -1 이 발생한 회수
|
|
//bool about_end = false;
|
|
endStop = false;
|
|
#endif // FIX_PLAYER_END_CLIP
|
|
|
|
while (!d.stop)
|
|
{
|
|
#if (RM_TESTING)
|
|
loopCount += 1;
|
|
#endif
|
|
|
|
#if (!ORIGINAL_PARAMETER)
|
|
#if (USE_DURATION_IN_THREAD)
|
|
usleep(1); // 32x speed 에서 처리되지 않음
|
|
if(durationSec > 0 && d.clock->value() > durationSec)
|
|
{
|
|
clock()->updateValue(durationSec); //external clock?
|
|
msleep(100);
|
|
break;
|
|
}
|
|
#endif
|
|
#endif // ORIGINAL_PARAMETER
|
|
|
|
#if (FORCE_BREAK_EOF)
|
|
if(forceEnd)
|
|
{
|
|
break;
|
|
}
|
|
#endif
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
if(endStop)
|
|
{
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
// if(playerID == 0)
|
|
// {
|
|
// qInfo() << __FUNCTION__ << __LINE__ << d.clock->value();
|
|
// }
|
|
|
|
processNextTask();
|
|
|
|
|
|
|
|
//TODO: why put it at the end of loop then stepForward() not work?
|
|
//processNextTask tryPause(timeout) and and continue outter loop
|
|
if (d.render_pts0 < 0) { // no pause when seeking
|
|
// 여기서 멈춤
|
|
if (tryPause()) { //DO NOT continue, or stepForward() will fail
|
|
}
|
|
else
|
|
{
|
|
if (isPaused())
|
|
{
|
|
#if (RM_TESTING)
|
|
_c1 += 1;
|
|
#endif
|
|
#if (VTHREAD_DEBUG)
|
|
if(playerID == 0) {
|
|
qInfo() << "d.render_pts0 < 0 continue" << playerID << __FUNCTION__ << __LINE__;
|
|
}
|
|
#endif
|
|
//qInfo() << playerID << "PAUSED:" << d.render_pts0 << __FUNCTION__ << __LINE__;
|
|
VTHREAD_CONTINUE; //timeout. process pending tasks
|
|
}
|
|
}
|
|
}
|
|
//#if (FIX_PLAYER_END_CLIP)
|
|
// try_pause_count = 0;
|
|
// qInfo() << try_pause_count << __FUNCTION__ << __LINE__;
|
|
//#endif
|
|
if (d.seek_requested)
|
|
{
|
|
d.seek_requested = false;
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "request seek video thread" << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
pkt = Packet(); // last decode failed and pkt is valid, reset pkt to force take the next packet if seek is requested
|
|
msleep(1);
|
|
}
|
|
else
|
|
{
|
|
// d.render_pts0 < 0 means seek finished here
|
|
if (d.clock->syncId() > 0) {
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "video thread wait to sync end for sync id:" << d.clock->syncId() << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
#if (FORCE_BREAK_EOF)
|
|
if (d.render_pts0 < 0 && sync_id > 0 && aboutToEnd == false)
|
|
#else
|
|
if (d.render_pts0 < 0 && sync_id > 0)
|
|
#endif
|
|
{
|
|
#if (ORIGINAL_PARAMETER)
|
|
msleep(10); // 10
|
|
#else
|
|
msleep(1);
|
|
#endif
|
|
v_a = 0;
|
|
#if (RM_TESTING)
|
|
_c2 += 1;
|
|
#endif
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
} else {
|
|
sync_id = 0;
|
|
}
|
|
}
|
|
if(!pkt.isValid() && !pkt.isEOF()) { // can't seek back if eof packet is read
|
|
pkt = d.packets.take(); //wait to dequeue
|
|
FLOG_VT << "PACKET TAKE:" << pkt.dts << FLOGE;
|
|
// TODO: push pts history here and reorder
|
|
}
|
|
|
|
if (pkt.isEOF())
|
|
{
|
|
wait_key_frame = false;
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "video thread gets an eof packet." << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
//#if (VTHREAD_DEBUG)
|
|
// qInfo() << pkt.pts << __FUNCTION__ << __LINE__;
|
|
//#endif
|
|
|
|
//qDebug() << pkt.position << " pts:" <<pkt.pts;
|
|
//Compare to the clock
|
|
if (!pkt.isValid())
|
|
{
|
|
// may be we should check other information. invalid packet can come from
|
|
wait_key_frame = true;
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "Invalid packet! flush video codec context!!!!!!!!!! video packet queue size:" << d.packets.size() << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
if (d.dec == NULL)
|
|
{
|
|
#if (RM_TESTING)
|
|
_c3 += 1;
|
|
#endif
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
d.dec->flush(); //d.dec instead of dec because d.dec maybe changed in processNextTask() but dec is not
|
|
d.render_pts0 = pkt.pts;
|
|
sync_id = pkt.position;
|
|
|
|
if (pkt.pts >= 0)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
// SEEK 시작
|
|
qInfo() << "video seek:" << d.render_pts0 << "id:" << sync_id << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
}
|
|
d.pts_history = ring<qreal>(d.pts_history.capacity());
|
|
v_a = 0;
|
|
#if (RM_TESTING)
|
|
_c4 += 1;
|
|
#endif
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
}
|
|
if (pkt.pts <= 0 && !pkt.isEOF() && pkt.data.size() > 0)
|
|
{
|
|
nb_no_pts++;
|
|
}
|
|
else
|
|
{
|
|
nb_no_pts = 0;
|
|
}
|
|
|
|
// PTS 없는 프레임이 반복될 경우 강제 처리
|
|
if (nb_no_pts > FORCE_FPS_NO_PTS_COUNT)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "the stream may have no pts. force fps to" << (d.force_fps < 0 ? -d.force_fps : 24) << d.force_fps << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
d.clock->setClockAuto(false);
|
|
d.clock->setClockType(AVClock::VideoClock);
|
|
if (d.force_fps < 0)
|
|
{
|
|
setFrameRate(-d.force_fps);
|
|
}
|
|
else if (d.force_fps == 0)
|
|
{
|
|
setFrameRate(24); // 24
|
|
}
|
|
}
|
|
if (d.clock->clockType() == AVClock::AudioClock)
|
|
{
|
|
sync_audio = true;
|
|
sync_video = false;
|
|
}
|
|
else if (d.clock->clockType() == AVClock::VideoClock)
|
|
{
|
|
sync_audio = false;
|
|
sync_video = true;
|
|
}
|
|
else // external
|
|
{
|
|
sync_audio = false;
|
|
sync_video = false;
|
|
}
|
|
|
|
// const qreal dts = pkt.dts + delay;
|
|
//#else
|
|
// 패킷은 Demux Thread 에서 생성(+2.0)한 대로 표시됨
|
|
// ((double)stopPosition)/1000000.0
|
|
//const qreal dts = pkt.dts;//qMin<qreal>(pkt.dts,stop_dts);
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
// if(playerID == 0 && dts_offset > 0 && pkt.pts > dts_offset) {
|
|
// pkt.pts -= dts_offset;
|
|
// pkt.dts = pkt.pts;
|
|
// }
|
|
#endif // #if (FIX_PLAYER_END_CLIP)
|
|
const qreal dts = pkt.pts;// dts;
|
|
|
|
|
|
//const qreal dts = qMax<qreal>(0.0,qMin<qreal>(pkt.dts,((double)stopPosition)/1000000.0)); //FIXME: pts and dts
|
|
|
|
// TODO: delta ref time
|
|
// if dts is invalid, diff can be very small (<0) and video will be decoded and rendered(display_wait is disabled for now) immediately
|
|
//qreal diff = dts > 0 ? dts - qMin<qreal>(d.clock->value(),stop_dts) + v_a : v_a;
|
|
qreal diff = dts > 0 ? dts - d.clock->value() + v_a : v_a;
|
|
|
|
// PAUSE 풀린 상태에서 VTHREAD 가 continue 동작함.!!!
|
|
FLOG_VT << "VTHREAD:" << dts << "PTS:" << pkt.pts << d.clock->value() << FLOGE;
|
|
|
|
if (pkt.isEOF())
|
|
{
|
|
#if (FORCE_BREAK_EOF)
|
|
diff = 0;
|
|
#else
|
|
diff = qMin<qreal>(1.0, qMax<qreal>(d.delay, 1.0/d.statistics->video_only.currentDisplayFPS()));
|
|
#endif
|
|
}
|
|
if (diff < 0 && sync_video)
|
|
{
|
|
diff = 0; // this ensures no frame drop
|
|
}
|
|
if (diff > kSyncThreshold)
|
|
{
|
|
nb_dec_fast++;
|
|
}
|
|
else
|
|
{
|
|
nb_dec_fast /= 2;
|
|
}
|
|
bool seeking = d.render_pts0 >= 0.0;
|
|
if (seeking)
|
|
{
|
|
nb_dec_slow = 0;
|
|
nb_dec_fast = 0;
|
|
//qInfo() << "SEEKING....";
|
|
}
|
|
#if (VTHREAD_DEBUG)
|
|
//qDebug("seeking:%d nb_fast/slow: %d/%d. diff: %f, delay: %f, dts: %f, clock: %f", seeking, nb_dec_fast, nb_dec_slow, diff, d.delay, dts, clock()->value());
|
|
#endif
|
|
if (d.delay < -0.5 && d.delay > diff)
|
|
{
|
|
if (!seeking)
|
|
{
|
|
// ensure video will not later than 2s
|
|
if (diff < -(SLOW_SKIP_SEC) || (nb_dec_slow > kNbSlowSkip && diff < -1.0 && !pkt.hasKeyFrame))
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
//qInfo() << "video is too slow. skip decoding until next key frame. diff:" << diff;
|
|
qInfo() << "video is too slow. skip decoding until next key frame. diff:" << diff << "dts:" << pkt.dts << "pts:" << pkt.pts << "clock:" << d.clock->value() << __FUNCTION__<< __LINE__;
|
|
#endif
|
|
// TODO: when to reset so frame drop flag can reset?
|
|
nb_dec_slow = 0;
|
|
wait_key_frame = true;
|
|
pkt = Packet();
|
|
v_a = 0;
|
|
// TODO: use discard flag
|
|
#if (RM_TESTING)
|
|
_c5 += 1;
|
|
#endif
|
|
VTHREAD_CONTINUE; // 배속설정하면 32배속에서 .. 반복됨
|
|
}
|
|
else
|
|
{
|
|
nb_dec_slow++;
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "frame slow count:" << nb_dec_slow << "diff:" << diff << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nb_dec_slow >= kNbSlowFrameDrop)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "decrease 1 slow frame:"<< nb_dec_slow <<__FUNCTION__<<__LINE__;
|
|
#endif
|
|
nb_dec_slow = qMax(0, nb_dec_slow-1); // nb_dec_slow < kNbSlowFrameDrop will reset decoder frame drop flag
|
|
}
|
|
}
|
|
// can not change d.delay after! we need it to comapre to next loop
|
|
d.delay = diff;
|
|
// 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.
|
|
if (seeking)
|
|
{
|
|
diff = 0; // TODO: here?
|
|
}
|
|
// 동기화가 되어 있지 않으면 여기시 동기화 처리함-> clock 을 잘못 설정하면 큰 값이 반영되어 대기시간이 몇초까지 늘어남
|
|
if (!sync_audio && diff > 0)
|
|
{
|
|
// wait to dts reaches
|
|
// d.force_fps>0: wait after decoded before deliver
|
|
#if (SKIP_WAIT_UNTIL_FIRST_FRAME)
|
|
if (d.force_fps <= 0 && drawFirstFrame == true)// || !qFuzzyCompare(d.clock->speed(), 1.0))
|
|
#else
|
|
if (d.force_fps <= 0)// || !qFuzzyCompare(d.clock->speed(), 1.0))
|
|
#endif
|
|
{
|
|
#if (IGNORE_BIG_FRAME_SKIP)
|
|
// 이거 H265용으로 빌드 했으나 텔레비트에서는 사용하지 말아야함!
|
|
// 큰 오차는 무시해버림 (seek 처리 도중에 다시 seek 발생하면서 clock 이 업데이트 된 상황임)
|
|
// 오디오 동기화 문제가 발생함
|
|
if (diff*1000UL > 1000)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "wait...." << diff*1000UL << " dts" << dts << "clock:" << d.clock->value() << " va:" << v_a;
|
|
#endif
|
|
d.clock->updateExternalClock(pkt.pts * 1000UL);
|
|
diff = 0;
|
|
}
|
|
#endif
|
|
|
|
#if (DEBUG_PLAYER_AVPACKET)
|
|
qInfo() << "-------------------------------------\nwaitAndCheck:" << diff*1000UL << "\ndts:" << dts;
|
|
#endif
|
|
|
|
#if (VTHREAD_DEBUG)
|
|
if(diff > 0.1) {
|
|
qInfo() << "WAIT(1):" << playerID << diff << " clock:" << d.clock->value() << "dts:" << dts << __FUNCTION__ << __LINE__;
|
|
}
|
|
#endif
|
|
// 플레이어 종료되지 않고 대기함..
|
|
// TODO: count decoding and filter time, or decode immediately but wait for display
|
|
#if (FORCE_BREAK_EOF)
|
|
if(forceEnd)
|
|
{
|
|
#if (RM_TESTING)
|
|
_feCount = loopCount;
|
|
#endif
|
|
break;
|
|
}
|
|
#endif
|
|
//qInfo() << ">>>>W" << diff << __FUNCTION__ << __LINE__;
|
|
waitAndCheck(diff*1000UL, dts);
|
|
}
|
|
//diff = 0; // TODO: can not change delay!
|
|
}
|
|
|
|
// clock update 가능함
|
|
do_not_update_next_clock = false;
|
|
|
|
// update here after wait. TODO: use decoded timestamp/guessed next pts?
|
|
#if (FIX_SYNC_TIME)
|
|
if(!seeking) // 추가 탐색시에는 시간 업데이트 하지 않는다.
|
|
#endif
|
|
{
|
|
// 여기서 clock update 을 업데이트 하니 문제가 됨
|
|
//qInfo() << "video time:" << dts;
|
|
d.clock->updateVideoTime(dts); // FIXME: dts or pts? // 실제 여기서 잘못된 position 이 발생함????
|
|
}
|
|
|
|
bool skip_render = false;
|
|
if (qAbs(diff) < 0.5)
|
|
{
|
|
if (diff < -kSyncThreshold)
|
|
{ //Speed up. drop frame?
|
|
#if (SKIP_FIRST_CORRUPT_FRAME)
|
|
VTHREAD_CONTINUE;
|
|
#endif
|
|
}
|
|
}
|
|
else if (!seeking) //when to drop off?
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "delay(1):" << diff << "clock:" << d.clock->value() << "pts:" << pkt.pts << "dts:" << pkt.dts << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
if (diff < 0)
|
|
{
|
|
if (nb_dec_slow > kNbSlowSkip)
|
|
{
|
|
skip_render = !pkt.hasKeyFrame && (nb_dec_slow %2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const double s = qMin<qreal>(0.01*(nb_dec_fast>>1), diff);
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "video too fast!!! sleep:" << s << " dec_fast:" << nb_dec_fast << " v_a" << v_a << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
//qInfo() << "video too fast!!! sleep:" << s << " dec_fast:" << nb_dec_fast << " v_a" << v_a << __FUNCTION__ << __LINE__;
|
|
//qInfo() << ">>>>W" << diff << __FUNCTION__ << __LINE__;
|
|
waitAndCheck(s*1000UL, dts);
|
|
|
|
diff = 0;
|
|
skip_render = false;
|
|
}
|
|
}
|
|
#if (USE_SKIP_COUNT)
|
|
if(drawFirstFrame == true && g_SkipCount != 0) // 최소 1회 render 되었으며 skip_count 가 설정되었을 경우
|
|
{
|
|
nSkip = (nSkip + 1) % g_SkipCount; // x 회당 1회 skip 처리함..
|
|
if(skip_render == false && seeking == false)
|
|
{
|
|
skip_render = (nSkip != 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//audio packet not cleaned up?
|
|
if (diff > 0 && diff < 1.0 && !seeking)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
if (diff > 0.1) {
|
|
qInfo() << "WAIT(3):" << playerID << diff << __FUNCTION__ << __LINE__;;
|
|
}
|
|
#endif
|
|
// can not change d.delay here! we need it to comapre to next loop
|
|
//qInfo() << ">>>>W" << diff << __FUNCTION__ << __LINE__;
|
|
waitAndCheck(diff*1000UL, dts);
|
|
//qInfo() << "WAIT>>>>" << diff << __FUNCTION__ << __LINE__;
|
|
}
|
|
if (wait_key_frame)
|
|
{
|
|
if (!pkt.hasKeyFrame)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "waiting for key frame. queue size:" << d.packets.size() << "pkt.data.size:" << pkt.data.size() << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
pkt = Packet();
|
|
v_a = 0;
|
|
#if (RM_TESTING)
|
|
_c6 += 1;
|
|
#endif
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
wait_key_frame = false;
|
|
}
|
|
|
|
// MAYBE not seeking. We should not drop the frames near the seek target. FIXME: use packet pts distance instead of -0.05 (20fps)
|
|
QVariantHash *dec_opt_old = dec_opt;
|
|
if (!seeking || pkt.pts - d.render_pts0 >= -0.05)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
if (seeking)
|
|
{
|
|
qInfo() << "seeking... pkt.pts - d.render_pts0:" << pkt.pts - d.render_pts0 << __FUNCTION__ << __LINE__;
|
|
}
|
|
#endif
|
|
if (nb_dec_slow < kNbSlowFrameDrop)
|
|
{
|
|
if (dec_opt == &d.dec_opt_framedrop)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "frame drop=>normal. nb_dec_slow:" << nb_dec_slow << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
dec_opt = &d.dec_opt_normal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dec_opt == &d.dec_opt_normal)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "frame drop=>noref. nb_dec_slow:" << nb_dec_slow << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
dec_opt = &d.dec_opt_framedrop;
|
|
}
|
|
}
|
|
}
|
|
else // seeking
|
|
{
|
|
if (seek_count > 0 && d.drop_frame_seek) {
|
|
if (dec_opt == &d.dec_opt_normal) {
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "seeking... pkt.pts - d.render_pts0:"<< (pkt.pts - d.render_pts0) << " dec_slow:" << nb_dec_slow << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
dec_opt = &d.dec_opt_framedrop;
|
|
}
|
|
} else {
|
|
seek_count = -1;
|
|
}
|
|
}
|
|
|
|
|
|
// decoder maybe changed in processNextTask(). code above MUST use d.dec but not dec
|
|
if (dec != static_cast<VideoDecoder*>(d.dec))
|
|
{
|
|
dec = static_cast<VideoDecoder*>(d.dec);
|
|
if (!pkt.hasKeyFrame)
|
|
{
|
|
wait_key_frame = true;
|
|
v_a = 0;
|
|
#if (RM_TESTING)
|
|
_c7 += 1;
|
|
#endif
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "wait_key_frame:" << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "decoder changed. decoding key frame" << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
}
|
|
// H265 stream 변경시 발생가능
|
|
if (dec == NULL || d.dec == NULL)
|
|
{
|
|
#if (RM_TESTING)
|
|
_c8 += 1;
|
|
#endif
|
|
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "dec == NULL || d.dec == NULL:" << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
if (dec_opt != dec_opt_old)
|
|
{
|
|
dec->setOptions(*dec_opt);
|
|
}
|
|
|
|
// 디코딩
|
|
if (!dec->decode(pkt))
|
|
{
|
|
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "decode failed" << pkt.dts << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
|
|
#if (SKIP_FIRST_CORRUPT_FRAME)
|
|
if(pkt.isEOF() || pkt.position == 0)
|
|
{
|
|
Q_EMIT eofDecoded();
|
|
break;
|
|
}
|
|
nb_no_pts = 0; // 처리하지 않으면 초기 프레임이 강제로 24FPS 로 초기화 됨
|
|
msleep(g_firstCorruptFrameSkipWait);
|
|
VTHREAD_CONTINUE;
|
|
#else
|
|
d.pts_history.push_back(d.pts_history.back());
|
|
qWarning("Decode video failed. undecoded: %d/%d", dec->undecodedSize(), pkt.data.size());
|
|
#if (FORCE_BREAK_EOF)
|
|
if (pkt.isEOF() && drawFirstFrame == true)
|
|
#else
|
|
if (pkt.isEOF())
|
|
#endif
|
|
{
|
|
Q_EMIT eofDecoded();
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "video decode eof done. d.render_pts0:" << d.render_pts0 << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
if (d.render_pts0 >= 0) {
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "video seek done at eof pts:" << d.pts_history.back() << "sync id:" << sync_id << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
d.render_pts0 = -1;
|
|
d.clock->syncEndOnce(sync_id);
|
|
#if !(REMOVE_EOF_SEEK_NOTIFY)
|
|
Q_EMIT seekFinished(qint64(d.pts_history.back()*1000.0));
|
|
#endif
|
|
if (seek_count == -1)
|
|
seek_count = 1;
|
|
else if (seek_count > 0)
|
|
seek_count++;
|
|
}
|
|
if (!pkt.position) // == 0?
|
|
{
|
|
#if (RM_TESTING)
|
|
_fePount = loopCount;
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
pkt = Packet();
|
|
v_a = 0; //?
|
|
#if (RM_TESTING)
|
|
_c9 += 1;
|
|
#endif
|
|
VTHREAD_CONTINUE;
|
|
#endif
|
|
}
|
|
// 초기 패킷이 디코딩 되지 않으면
|
|
// VDI << "pkt.pts:" << pkt.pts << " dst:" << pkt.dts << " duration:" << pkt.duration;
|
|
|
|
// reduce here to ensure to decode the rest data in the next loop
|
|
// 다음 데이터로 이동
|
|
if (!pkt.isEOF())
|
|
{
|
|
//#if (DEBUG_PLAYER_AVPACKET)
|
|
// qDebug() << "-----------------------------------" <<
|
|
// "\npkt.data.size()" << pkt.data.size() <<
|
|
// "\ndec->undecodedSize()" << dec->undecodedSize() <<
|
|
// "\nskip:" << pkt.data.size() - dec->undecodedSize();
|
|
//#endif
|
|
pkt.skip(pkt.data.size() - dec->undecodedSize());
|
|
}
|
|
//qInfo() << playerID << "clock()->value:" << clock()->value() << __FUNCTION__;
|
|
|
|
VideoFrame frame = dec->frame();
|
|
if (!frame.isValid())
|
|
{
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
if(drawFirstFrame == false) {
|
|
frame_offset++;
|
|
}
|
|
#endif // #if (FIX_PLAYER_END_CLIP)
|
|
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "frame invalid" << pkt.dts << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
if (pkt_data == pkt.data.constData()) //FIXME: for libav9. what about other versions?
|
|
{
|
|
pkt = Packet();
|
|
}
|
|
else
|
|
{
|
|
pkt_data = pkt.data.constData();
|
|
}
|
|
v_a = 0; //?
|
|
//g_invalid_packet_count++;
|
|
#if (RM_TESTING)
|
|
// 후방 그려지지 않는 경우는 frame invalid 가 9회...
|
|
_c10 += 1;
|
|
#endif
|
|
FLOG_VT << "!!!! INVALID FRAME" << FLOGE;
|
|
// PAUSE 종료시 여기서 continue 반복되어 영상이 멈춤
|
|
//processNextTask();
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "DRAW FRAME" << pkt.dts << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
|
|
pkt_data = pkt.data.constData();
|
|
|
|
// 초기 2초정도 깨진 프레임이 존재하며 PTS,DTS 가 이상하게 나와도
|
|
// frame 의 timestamp 는 정상적으로 추출됨
|
|
#if (PREVENT_OVER_DURATION_RENDER || !(SUPPORT_H265)) // 는 동기화
|
|
//if (frame.timestamp() <= 0)
|
|
#endif // SUPPORT_H265
|
|
{
|
|
frame.setPTS(pkt.pts); // pkt.pts is wrong. >= real timestamp
|
|
}
|
|
const qreal pts = frame.pts();// timestamp();
|
|
//FLOG_OT << pts << dts << __FUNCTION__ << __LINE__;
|
|
|
|
d.pts_history.push_back(pts);
|
|
// seek finished because we can ensure no packet before seek decoded when render_pts0 is set
|
|
//qDebug("pts0: %f, pts: %f, clock: %d", d.render_pts0, pts, d.clock->clockType());
|
|
if (d.render_pts0 >= 0.0)
|
|
{
|
|
if (pts < d.render_pts0)
|
|
{
|
|
if (!pkt.isEOF())
|
|
{
|
|
pkt = Packet();
|
|
}
|
|
v_a = 0;
|
|
// DRAWING 처리하지 못하고 지
|
|
// RENDER PTS: 4.531 PTS: 3.756 FAV::VideoThread::run 984
|
|
FLOG_VT << "RENDER PTS:" << d.render_pts0 << "PTS:" << pts << __FUNCTION__ << __LINE__;
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
d.render_pts0 = -1;
|
|
|
|
d.clock->syncEndOnce(sync_id);
|
|
d.clock->updateExternalClock(pts*1000.0); // external clock
|
|
do_not_update_next_clock = true; // 처리 완료시까지 clock update 금지
|
|
//qInfo() << "SEEK END";
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "video seek finished:" << pts << "id:" << sync_id << "clock:" << d.clock->value() << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
|
|
// 여기서 SEEK 문제 발생함
|
|
// 0 으로 SEEK 하면 33 으로 이동됨
|
|
#if (FIX_SYNC_TIME)
|
|
Q_EMIT seekFinished(qint64((pts-pkt.duration)*1000.0)); // FPS 5 = 0.2
|
|
#else
|
|
Q_EMIT seekFinished(qint64(pts*1000.0));
|
|
#endif
|
|
|
|
if (seek_count == -1)
|
|
seek_count = 1;
|
|
else if (seek_count > 0)
|
|
seek_count++;
|
|
}
|
|
|
|
// PAUSE 종료 후 여기까지 오지 않고...
|
|
FLOG_VT << "DRAW_VIDEO:" << "FR:" << pts << "PKT:" << pkt.pts << FLOGE;
|
|
|
|
|
|
#if (USE_SKIP_COUNT)
|
|
drawFirstFrame = true;
|
|
#endif
|
|
|
|
Q_ASSERT(d.statistics);
|
|
d.statistics->video.current_time = QTime(0, 0, 0).addMSecs(int(pts * 1000.0)); //TODO: is it expensive?
|
|
applyFilters(frame);
|
|
|
|
//while can pause, processNextTask, not call outset.puase which is deperecated
|
|
while (d.outputSet->canPauseThread())
|
|
{
|
|
d.outputSet->pauseThread(100);
|
|
//tryPause(100);
|
|
processNextTask();
|
|
}
|
|
//qDebug("force fps: %f dt: %d", d.force_fps, d.force_dt);
|
|
if (d.force_dt > 0) {// && qFuzzyCompare(d.clock->speed(), 1.0)) {
|
|
const qint64 now = QDateTime::currentMSecsSinceEpoch();
|
|
const qint64 delta = qint64(d.force_dt) - (now - last_deliver_time);
|
|
if (frame.pts() <= 0)
|
|
{
|
|
// TODO: what if seek happens during playback?
|
|
const int msecs_started(now + qMax(0LL, delta) - start_time);
|
|
frame.setPTS(qreal(msecs_started)/1000.0);
|
|
clock()->updateValue(frame.pts()); //external clock?
|
|
}
|
|
if (delta > 0LL) { // limit up bound?
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "WAIT(4):" << playerID << diff << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
waitAndCheck((ulong)delta, -1); // wait and not compare pts-clock
|
|
}
|
|
}
|
|
else if (false) //FIXME: may block a while when seeking
|
|
{
|
|
const qreal display_wait = pts - clock()->value();
|
|
if (!seeking && display_wait > 0.0) {
|
|
// wait to pts reaches. TODO: count rendering time
|
|
//qDebug("wait %f to display for pts %f-%f", display_wait, pts, clock()->value());
|
|
if (display_wait < 1.0)
|
|
{
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "WAIT(5):" << playerID << diff << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
//qInfo() << ">>>>W" << display_wait << __FUNCTION__ << __LINE__;
|
|
waitAndCheck(display_wait*1000UL, pts); // TODO: count decoding and filter time
|
|
}
|
|
}
|
|
}
|
|
|
|
// SKIP 은 위 내용 처리하고 해야함.
|
|
//qInfo() << "SKIP:" << skip_render;
|
|
if (skip_render)
|
|
{
|
|
|
|
#if (VTHREAD_DEBUG)
|
|
qInfo() << "skip rendering :" << pts << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
// pkt = Packet();
|
|
// v_a = 0;
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
|
|
//#if (FIXED_FPS_DURATION)
|
|
// bool isStopPosition = (stopPosition > 0 && (qint64)(frame.timestamp() * 1000000) > stopPosition);
|
|
// // 후방 깨진영역 그리지 않는다.
|
|
// if(isStopPosition == true && playerID == 1)
|
|
// {
|
|
// //qInfo() << __FUNCTION__ << "SLEEP" << (qint64)(frame.timestamp() * 1000000) << " stop:" << stopPosition << " pkt.pts:" << pkt.pts;
|
|
// msleep(1);
|
|
// VTHREAD_CONTINUE;
|
|
// }
|
|
//#endif
|
|
|
|
#if (PENTA_CHANNEL)
|
|
//qInfo() << "DRAW:" << playerID << __FUNCTION__ << __LINE__;
|
|
#endif // PENTA_CHANNEL
|
|
//qInfo() << "V:" << frame.timestamp();
|
|
// no return even if d.stop is true. ensure frame is displayed. otherwise playing an image may be failed to display
|
|
if (!deliverVideoFrame(frame))
|
|
{
|
|
VTHREAD_CONTINUE;
|
|
}
|
|
#if (RM_TESTING)
|
|
displyedFrame += 1;
|
|
#endif
|
|
|
|
#if (PREVENT_OVER_DURATION_RENDER)
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
if(frame.pts() < 0) {
|
|
|
|
//qInfo() << "NEGATIVE..." << __FUNCTION__ << __LINE__;
|
|
QThread::msleep(10);
|
|
//qInfo() << "FRAME OFFSET:" << ms << "frame_offset:" << frame_offset << "dts_offset:" << dts_offset << __FUNCTION__ << __LINE__;
|
|
}
|
|
//qInfo() << "ID:" << playerID << "TS:" << frame.timestamp() << "DU" << duration << "CLK:" << clock()->value() << "offset:" << dts_offset << "nf" << negative_frame_count << "RPTS:" << d.render_pts0 << __FUNCTION__ << __LINE__;
|
|
#endif //
|
|
//qInfo() << playerID << frame.timestamp() << "duration:" << duration << __FUNCTION__ << __LINE__;
|
|
// pkt.isEOF() || frame.timestamp() > duration || frame.timestamp() < 0 ||
|
|
if(frame.pts() < 0 && negative_frame_count >= (frame_offset-1) && clock()->value() + dts_offset + 0.1 > duration) {
|
|
qInfo() << "END OF VIDEO THREAD" << __FUNCTION__ << __LINE__;
|
|
Q_EMIT frameEnded();
|
|
//qInfo() << playerID << frame.timestamp() << "duration:" << duration << "pkt.pts:" << pkt.dts << "clock:" << clock()->value() << __FUNCTION__ << __LINE__;
|
|
break;
|
|
}
|
|
#endif // PREVENT_OVER_DURATION_RENDER
|
|
|
|
// if(playerID == 0)
|
|
// {
|
|
// qInfo() << "deliverVideoFrame:" << frame.timestamp();
|
|
// }
|
|
|
|
|
|
|
|
//#if (FIXED_FPS_DURATION)
|
|
// if(isStopPosition && playerID == 0)
|
|
// {
|
|
// Q_EMIT frameEnded();
|
|
// }
|
|
//#endif
|
|
|
|
#if (FIRST_FRAME_NOTIFY)
|
|
if(bFirstFrameDraw == false)
|
|
{
|
|
#if (FIX_PLAYER_END_CLIP)
|
|
dts_offset = pts;
|
|
//qInfo() << "FIRST FRAME:" << playerID << "CLOCK:" << clock()->value() << __FUNCTION__ << __LINE__ ;// << g_invalid_packet_count;
|
|
#endif // #if (FIX_PLAYER_END_CLIP)
|
|
bFirstFrameDraw = true;
|
|
emit firstFrameNotify(pts);
|
|
}
|
|
#endif
|
|
|
|
//qDebug("clock.diff: %.3f", d.clock->diff());
|
|
if (d.force_dt > 0)
|
|
last_deliver_time = QDateTime::currentMSecsSinceEpoch();
|
|
// TODO: store original frame. now the frame is filtered and maybe converted to renderer perferred format
|
|
d.displayed_frame = frame;
|
|
if (d.clock->clockType() == AVClock::AudioClock)
|
|
{
|
|
const qreal v_a_ = frame.pts() - d.clock->value();
|
|
if (!qFuzzyIsNull(v_a_)) {
|
|
if (v_a_ < -0.1) {
|
|
if (v_a <= v_a_)
|
|
v_a += -0.01;
|
|
else
|
|
v_a = (v_a_ +v_a)*0.5;
|
|
} else if (v_a_ < -0.002) {
|
|
v_a += -0.001;
|
|
} else if (v_a_ < 0.002) {
|
|
} else if (v_a_ < 0.1) {
|
|
v_a += 0.001;
|
|
} else {
|
|
if (v_a >= v_a_)
|
|
v_a += 0.01;
|
|
else
|
|
v_a = (v_a_ +v_a)*0.5;
|
|
}
|
|
|
|
if (v_a < -2 || v_a > 2)
|
|
v_a /= 2.0;
|
|
}
|
|
//qDebug("v_a:%.4f, v_a_: %.4f", v_a, v_a_);
|
|
}
|
|
}
|
|
#if 0 // why off??
|
|
if (d.stop) {// user stop
|
|
// decode eof?
|
|
qInfo("decoding eof...");
|
|
|
|
while (d.dec && d.dec->decode(Packet::createEOF())) {d.dec->flush();}
|
|
}
|
|
#endif
|
|
d.packets.clear();
|
|
#if (PENTA_CHANNEL)
|
|
//qInfo() << "EXIT:" << playerID << __FUNCTION__ << __LINE__;
|
|
#endif // PENTA_CHANNEL
|
|
#if (RM_TESTING)
|
|
if( displyedFrame == 0)
|
|
{
|
|
// 전방
|
|
// 초기: DS Loop: 30 C: 0 0 0 2 0 0 0 0 0 16
|
|
// 1회 이후: DS Loop: 27 C: 0 0 0 2 0 0 0 0 0 16
|
|
|
|
// 후방
|
|
// 초기: DS Loop: 8521 C: 0 0 0 8488 0 0 0 0 4 16 , DS Loop: 6457 C: 0 0 0 6424 0 0 0 0 4 16
|
|
// 초기는 PACKET 이 valid 하지 않음???
|
|
// 1회 이후: DS Loop: 31 C: 0 0 0 2 0 0 0 0 0 16 , _feCount == 0
|
|
// 문제 발생시: DS Loop: 13 C: 0 0 0 1 0 0 0 0 2 9 --> LOOP COUNT 가 너무 적음.. _feCount == 0
|
|
// DS: 0 LOOP: 22 C: 0 0 0 1 0 0 0 0 4 16 -->
|
|
|
|
// qInfo() << "DS:" << playerID << "FORCE BREAK COUNT:" << _feCount << _fePount << \
|
|
// "FRAME:" << displyedFrame << " LOOP:" << loopCount << \
|
|
// "C:" << _c1 << _c2 << _c3 << _c4 << _c5 << _c6 << _c7 << _c8 << _c9 << _c10;
|
|
}
|
|
// qInfo() << "EXIT" << playerID << __FUNCTION__ << __LINE__;
|
|
#endif
|
|
qDebug("Video thread stops running...");
|
|
}
|
|
#endif // #else // PLAY_SYNC_FIX2
|
|
|
|
} //namespace FAV
|