/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * 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 #include "Logger.h" #include "fav_common.h" #include "fav_profile.h" #include 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(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 outputs = d.outputSet->outputs(); VideoRenderer *vo = 0; if (!outputs.isEmpty()) vo = static_cast(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(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(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(d.dec)) // { // dec = static_cast(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(1.0, qMax(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(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(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:" <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(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(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(0.0,qMin(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(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(1.0, qMax(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(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(d.dec)) { dec = static_cast(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