mirror of
https://github.com/wang-bin/QtAV.git
synced 2026-10-25 17:40:26 +08:00
simplify seek. emit seekFinished correctly
TODO: refine playNextFrame()
This commit is contained in:
+8
-10
@@ -160,13 +160,6 @@ void AVDemuxThread::seek(qint64 pos, SeekType type)
|
||||
void AVDemuxThread::seekInternal(qint64 pos, SeekType type)
|
||||
{
|
||||
AVThread* av[] = { audio_thread, video_thread};
|
||||
for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) {
|
||||
AVThread *t = av[i];
|
||||
if (!t)
|
||||
continue;
|
||||
t->packetQueue()->blockEmpty(false); // was buffering
|
||||
t->packetQueue()->clear();
|
||||
}
|
||||
qDebug("seek to %s %lld ms (%f%%)", QTime(0, 0, 0).addMSecs(pos).toString().toUtf8().constData(), pos, double(pos - demuxer->startTime())/double(demuxer->duration())*100.0);
|
||||
demuxer->setSeekType(type);
|
||||
demuxer->seek(pos);
|
||||
@@ -176,10 +169,15 @@ void AVDemuxThread::seekInternal(qint64 pos, SeekType type)
|
||||
if (!t)
|
||||
continue;
|
||||
t->packetQueue()->clear();
|
||||
t->packetQueue()->setBlocking(true); // blockEmpty was false when eof is read.
|
||||
// TODO: the first frame (key frame) will not be decoded correctly if flush() is called.
|
||||
if (type == AccurateSeek)
|
||||
t->packetQueue()->put(Packet());
|
||||
if (type == AccurateSeek) {
|
||||
Packet pkt;
|
||||
pkt.pts = qreal(pos)/1000.0;
|
||||
//qDebug("put seek packet. %d/%d-%d, progress: %.3f", pb->buffered(), pb->bufferValue(), pb->bufferMax(), pb->bufferProgress());
|
||||
t->packetQueue()->setBlocking(false); // aqueue bufferValue can be small (1), we can not put and take
|
||||
t->packetQueue()->put(pkt);
|
||||
}
|
||||
t->packetQueue()->setBlocking(true); // blockEmpty was false when eof is read.
|
||||
}
|
||||
if (isPaused() && (video_thread || audio_thread)) {
|
||||
AVThread *thread = video_thread ? video_thread : audio_thread;
|
||||
|
||||
@@ -811,14 +811,6 @@ void AVPlayer::setPosition(qint64 position)
|
||||
d->seeking = true;
|
||||
d->seek_target = position;
|
||||
qreal s = (qreal)pos_pts/1000.0;
|
||||
if (seekType() == AccurateSeek) {
|
||||
if (d->athread) {
|
||||
d->athread->skipRenderUntil(s);
|
||||
}
|
||||
if (d->vthread) {
|
||||
d->vthread->skipRenderUntil(s);
|
||||
}
|
||||
}
|
||||
masterClock()->updateValue(double(pos_pts)/1000.0); //what is duration == 0
|
||||
masterClock()->updateExternalClock(pos_pts); //in msec. ignore usec part using t/1000
|
||||
d->read_thread->seek(pos_pts, seekType());
|
||||
|
||||
+1
-25
@@ -108,30 +108,6 @@ void AVThread::scheduleTask(QRunnable *task)
|
||||
d_func().tasks.put(task);
|
||||
}
|
||||
|
||||
void AVThread::skipRenderUntil(qreal pts)
|
||||
{
|
||||
/*
|
||||
* Lock here is useless because in Audio/VideoThread, the lock scope is very small.
|
||||
* So render_pts0 may be reset to 0 after set here
|
||||
*/
|
||||
DPTR_D(AVThread);
|
||||
class SetRenderPTS0Task : public QRunnable {
|
||||
public:
|
||||
SetRenderPTS0Task(qreal* pts0, qreal value)
|
||||
: ptr(pts0)
|
||||
, pts(value)
|
||||
{}
|
||||
void run() {
|
||||
*ptr = pts;
|
||||
}
|
||||
private:
|
||||
qreal *ptr;
|
||||
qreal pts;
|
||||
};
|
||||
|
||||
scheduleTask(new SetRenderPTS0Task(&d.render_pts0, pts));
|
||||
}
|
||||
|
||||
// TODO: shall we close decoder here?
|
||||
void AVThread::stop()
|
||||
{
|
||||
@@ -245,7 +221,7 @@ void AVThread::resetState()
|
||||
DPTR_D(AVThread);
|
||||
pause(false);
|
||||
d.tasks.clear();
|
||||
d.render_pts0 = 0;
|
||||
d.render_pts0 = -1;
|
||||
d.stop = false;
|
||||
d.packets.setBlocking(true);
|
||||
d.packets.clear();
|
||||
|
||||
@@ -75,8 +75,6 @@ public:
|
||||
|
||||
// TODO: resample, resize task etc.
|
||||
void scheduleTask(QRunnable *task);
|
||||
//only decode video without display or skip decode audio until pts reaches
|
||||
void skipRenderUntil(qreal pts);
|
||||
|
||||
public slots:
|
||||
virtual void stop();
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public:
|
||||
, delay(0)
|
||||
, statistics(0)
|
||||
, ready(false)
|
||||
, render_pts0(0)
|
||||
, render_pts0(-1)
|
||||
{
|
||||
tasks.blockFull(false);
|
||||
}
|
||||
|
||||
+12
-2
@@ -108,9 +108,11 @@ void AudioThread::run()
|
||||
Q_UNUSED(locker);
|
||||
if (d.dec) //maybe set to null in setDecoder()
|
||||
d.dec->flush();
|
||||
d.render_pts0 = pkt.pts;
|
||||
continue;
|
||||
}
|
||||
qreal dts = pkt.dts; //FIXME: pts and dts
|
||||
// no key frame for audio. so if pts reaches, try decode and skip render if got frame pts does not reach
|
||||
bool skip_render = pkt.pts < d.render_pts0;
|
||||
// audio has no key frame, skip rendering equals to skip decoding
|
||||
if (skip_render) {
|
||||
@@ -130,7 +132,6 @@ void AudioThread::run()
|
||||
pkt = Packet(); //mark invalid to take next
|
||||
continue;
|
||||
}
|
||||
d.render_pts0 = 0;
|
||||
if (is_external_clock) {
|
||||
d.delay = dts - d.clock->value();
|
||||
/*
|
||||
@@ -223,7 +224,16 @@ void AudioThread::run()
|
||||
#if USE_AUDIO_FRAME
|
||||
AudioFrame frame(dec->frame());
|
||||
if (frame) {
|
||||
//TODO: apply filters here
|
||||
if (d.render_pts0 >= 0.0) { // seeking
|
||||
if (frame.timestamp() < d.render_pts0) {
|
||||
qDebug("skip audio rendering: %f-%f", frame.timestamp(), d.render_pts0);
|
||||
d.clock->updateValue(frame.timestamp());
|
||||
pkt = Packet();
|
||||
continue;
|
||||
}
|
||||
d.render_pts0 = -1.0;
|
||||
Q_EMIT seekFinished(qint64(frame.timestamp()*1000.0));
|
||||
}
|
||||
if (has_ao) {
|
||||
applyFilters(frame);
|
||||
frame.setAudioResampler(dec->resampler()); //!!!
|
||||
|
||||
+6
-7
@@ -66,7 +66,7 @@ Packet Packet::createEOF()
|
||||
|
||||
bool Packet::isEOF() const
|
||||
{
|
||||
return data == "eof";
|
||||
return data == "eof" && pts < 0.0 && dts < 0.0;
|
||||
}
|
||||
|
||||
Packet Packet::fromAVPacket(const AVPacket *avpkt, double time_base)
|
||||
@@ -131,7 +131,7 @@ bool Packet::fromAVPacket(Packet* pkt, const AVPacket *avpkt, double time_base)
|
||||
#if AVPACKET_REF
|
||||
av_packet_ref(p, (AVPacket*)avpkt); //properties are copied internally
|
||||
// add ref without copy, bytearray does not copy either. bytearray options linke remove() is safe. omit FF_INPUT_BUFFER_PADDING_SIZE
|
||||
pkt->data =QByteArray::fromRawData((const char*)p->data, p->size);
|
||||
pkt->data = QByteArray::fromRawData((const char*)p->data, p->size);
|
||||
#else
|
||||
if (avpkt->data) {
|
||||
// copy packet data. packet will be reset after AVDemuxer.readFrame() and in next av_read_frame
|
||||
@@ -165,10 +165,10 @@ bool Packet::fromAVPacket(Packet* pkt, const AVPacket *avpkt, double time_base)
|
||||
Packet::Packet()
|
||||
: hasKeyFrame(false)
|
||||
, isCorrupt(false)
|
||||
, pts(0)
|
||||
, duration(0)
|
||||
, dts(0)
|
||||
, position(0)
|
||||
, pts(-1)
|
||||
, duration(-1)
|
||||
, dts(-1)
|
||||
, position(-1)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -232,5 +232,4 @@ const AVPacket *Packet::asAVPacket() const
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
} //namespace QtAV
|
||||
|
||||
+22
-45
@@ -298,7 +298,6 @@ void VideoThread::run()
|
||||
int nb_dec_slow = 0;
|
||||
int nb_dec_fast = 0;
|
||||
|
||||
bool is_pkt_bf_seek = true; // when seeking, a packet token from queue maybe before queue.clear() and packet is old. we may skip compare, wait and decode.
|
||||
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.
|
||||
@@ -307,10 +306,10 @@ void VideoThread::run()
|
||||
const int kNbSlowSkip = 120; // about 1s for 120fps video
|
||||
// kNbSlowFrameDrop: if video frame slow count > kNbSlowFrameDrop, skip decoding nonref frames. only some of ffmpeg based decoders support it.
|
||||
const int kNbSlowFrameDrop = 10;
|
||||
int seek_done_count = 0; // seek_done_count > 1 means seeking is really finished. theorically can't be >1 if seeking!
|
||||
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();
|
||||
bool skip_render = false; // keep true if decoded frame does not reach desired time
|
||||
while (!d.stop) {
|
||||
processNextTask();
|
||||
//TODO: why put it at the end of loop then playNextFrame() not work?
|
||||
@@ -336,6 +335,7 @@ void VideoThread::run()
|
||||
wait_key_frame = true;
|
||||
qDebug("Invalid packet! flush video codec context!!!!!!!!!! video packet queue size: %d", d.packets.size());
|
||||
dec->flush();
|
||||
d.render_pts0 = pkt.pts;
|
||||
continue;
|
||||
}
|
||||
if (d.clock->clockType() == AVClock::AudioClock) {
|
||||
@@ -358,14 +358,13 @@ void VideoThread::run()
|
||||
} else {
|
||||
nb_dec_fast /= 2;
|
||||
}
|
||||
bool seeking = !qFuzzyIsNull(d.render_pts0);
|
||||
bool seeking = d.render_pts0 >= 0.0;
|
||||
if (seeking) {
|
||||
nb_dec_slow = 0;
|
||||
nb_dec_fast = 0;
|
||||
}
|
||||
//qDebug("nb_fast: %d", nb_dec_fast);
|
||||
//qDebug("nb_fast: %d. diff: %f, dts: %f, clock: %f", nb_dec_fast, diff, dts, clock()->value());
|
||||
if (d.delay < -0.5 && d.delay > diff) {
|
||||
seeking = !qFuzzyIsNull(d.render_pts0);
|
||||
if (!seeking) {
|
||||
// ensure video will not later than 2s
|
||||
if (diff < -2 || (nb_dec_slow > kNbSlowSkip && diff < -1.0 && !pkt.hasKeyFrame)) {
|
||||
@@ -393,7 +392,10 @@ void VideoThread::run()
|
||||
*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.
|
||||
*/
|
||||
bool skip_render = dts < d.render_pts0; // FIXME: check after decode()
|
||||
if (pkt.pts < d.render_pts0) // skip rendering until decoded frame reaches desired pts
|
||||
skip_render = true;
|
||||
if (seeking)
|
||||
diff = 0; // TODO: here?
|
||||
if (!sync_audio && diff > 0) {
|
||||
// wait to dts reaches
|
||||
waitAndCheck(diff*1000UL, dts); // TODO: count decoding and filter time
|
||||
@@ -401,7 +403,6 @@ void VideoThread::run()
|
||||
}
|
||||
// update here after wait
|
||||
d.clock->updateVideoTime(dts); // FIXME: dts or pts?
|
||||
seeking = !qFuzzyIsNull(d.render_pts0);
|
||||
if (qAbs(diff) < 0.5) {
|
||||
if (diff < -kSyncThreshold) { //Speed up. drop frame?
|
||||
//continue;
|
||||
@@ -425,41 +426,25 @@ void VideoThread::run()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// video too fast if old packet before seek backward compared with new audio packet after seek backward
|
||||
// what about video too late?
|
||||
if (is_pkt_bf_seek && diff > 2.0) {
|
||||
// if seeking, we can not continue without decoding
|
||||
// FIXME: also happens after seek but not seeking. so must check seek_done_count
|
||||
is_pkt_bf_seek = false;
|
||||
if (seek_done_count <= 1) { // is seeking
|
||||
qDebug("old video packet before seek detected!!!!!!!!!!!!!");
|
||||
pkt = Packet();
|
||||
continue; // seeking and this v packet is before seeking
|
||||
}
|
||||
}
|
||||
const double s = qMin<qreal>(0.01*(nb_dec_fast>>1), diff);
|
||||
qWarning("video too fast!!! sleep %.2f s, nb fast: %d", s, nb_dec_fast);
|
||||
waitAndCheck(s*1000UL, dts);
|
||||
diff = 0;
|
||||
}
|
||||
}
|
||||
seeking = !qFuzzyIsNull(d.render_pts0);
|
||||
//qDebug("d.render_pts0: %f, seeking: %d", d.render_pts0, seeking);
|
||||
//audio packet not cleaned up?
|
||||
if (diff > 0 && diff < 1.0 && !seeking) {
|
||||
// can not change d.delay here! we need it to comapre to next loop
|
||||
waitAndCheck(diff*1000UL, dts);
|
||||
}
|
||||
if (wait_key_frame) {
|
||||
if (pkt.hasKeyFrame)
|
||||
wait_key_frame = false;
|
||||
else {
|
||||
if (!pkt.hasKeyFrame) {
|
||||
qDebug("waiting for key frame. queue size: %d. pkt.size: %d", d.packets.size(), pkt.data.size());
|
||||
pkt = Packet();
|
||||
continue;
|
||||
}
|
||||
wait_key_frame = false;
|
||||
}
|
||||
seeking = !qFuzzyIsNull(d.render_pts0);
|
||||
QVariantHash *dec_opt_old = dec_opt;
|
||||
if (!seeking) { // MAYBE not seeking
|
||||
if (nb_dec_slow < kNbSlowFrameDrop) {
|
||||
@@ -505,30 +490,23 @@ void VideoThread::run()
|
||||
qWarning() << "invalid video frame from decoder";
|
||||
continue;
|
||||
}
|
||||
is_pkt_bf_seek = true;
|
||||
if (skip_render) {
|
||||
pkt = Packet(); //mark invalid to take next
|
||||
continue;
|
||||
}
|
||||
if (frame.timestamp() == 0)
|
||||
frame.setTimestamp(pkt.pts); // pkt.pts is wrong. >= real timestamp
|
||||
const qreal pts = frame.timestamp();
|
||||
// can not check only pts > render_pts0 for seek backward. delta can not be too small(smaller than 1/fps)
|
||||
// FIXME: what if diff too large?
|
||||
if (d.render_pts0 > 0 && pts > d.render_pts0) {
|
||||
if (seek_done_count > 1)
|
||||
seek_done_count = 0;
|
||||
seek_done_count++;
|
||||
if (seek_done_count > 1) { // theorically can't be >1 if seeking!
|
||||
qDebug("reset render_pts0");
|
||||
d.render_pts0 = 0;
|
||||
Q_EMIT seekFinished(qint64(pts*1000.0));
|
||||
// seek finished because we can ensure no packet before seek decoded when render_pts0 is set
|
||||
//qDebug("pts0: %f, pts: %f", d.render_pts0, pts);
|
||||
if (d.render_pts0 >= 0.0) {
|
||||
if (pts < d.render_pts0) {
|
||||
pkt = Packet();
|
||||
continue;
|
||||
}
|
||||
d.render_pts0 = -1;
|
||||
Q_EMIT seekFinished(qint64(pts*1000.0));
|
||||
if (seek_count == -1)
|
||||
seek_count = 1;
|
||||
else if (seek_count > 0)
|
||||
seek_count++;
|
||||
}
|
||||
if (seek_count == -1 && seek_done_count > 0)
|
||||
seek_count = 1;
|
||||
else if (seek_count > 0)
|
||||
seek_count++;
|
||||
Q_ASSERT(d.statistics);
|
||||
d.statistics->video.current_time = QTime(0, 0, 0).addMSecs(int(pts * 1000.0)); //TODO: is it expensive?
|
||||
applyFilters(frame);
|
||||
@@ -564,7 +542,6 @@ void VideoThread::run()
|
||||
++d.force_dt;
|
||||
}
|
||||
} else if (false) { //FIXME: may block a while when seeking
|
||||
seeking = !qFuzzyIsNull(d.render_pts0);
|
||||
const qreal display_wait = pts - clock()->value();
|
||||
if (!seeking && display_wait > 0.0) {
|
||||
// wait to pts reaches. TODO: count rendering time
|
||||
|
||||
Reference in New Issue
Block a user