void AVDemuxThread::stepBackward() { if (!video_thread) return; AVThread *t = video_thread; const qreal pre_pts = video_thread->previousHistoryPts(); if (pre_pts == 0.0) { qWarning("can not get previous pts"); return; } end = false; // queue maybe blocked by put() if (audio_thread) { audio_thread->packetQueue()->clear(); // will put new packets before task run } class stepBackwardTask : public QRunnable { public: stepBackwardTask(AVDemuxThread *dt, qreal t) : demux_thread(dt) , pts(t) {} void run() { AVThread *avt = demux_thread->videoThread(); avt->packetQueue()->clear(); // clear here if (pts <= 0) { demux_thread->demuxer->seek(qint64(-pts*1000.0) - 1000LL); QVector<qreal> ts; qreal t = -1.0; while (t < -pts) { demux_thread->demuxer->readFrame(); if (demux_thread->demuxer->stream() != demux_thread->demuxer->videoStream()) continue; t = demux_thread->demuxer->packet().pts; ts.push_back(t); } ts.pop_back(); pts = ts.back(); // FIXME: sometimes can not seek to the previous pts, the result pts is always current pts } qDebug("step backward: %lld, %f", qint64(pts*1000.0), pts); demux_thread->video_thread->setDropFrameOnSeek(false); demux_thread->seekInternal(qint64(pts*1000.0), AccurateSeek); } private: AVDemuxThread *demux_thread; qreal pts; }; pause(true); t->packetQueue()->clear(); // will put new packets before task run t->packetQueue(); Packet pkt; pkt.pts = pre_pts; t->packetQueue()->put(pkt); // clear and put a seek packet to ensure not frames other than previous frame will be decoded and rendered video_thread->pause(false); newSeekRequest(new stepBackwardTask(this, pre_pts)); }
virtual void call() { if (!mDemuxThread) return; if (mDemuxThread->isEnd()) return; mDemuxThread->updateBufferState(); // ensure detect buffering immediately AVThread *thread = mDemuxThread->videoThread(); //qDebug("try wake up video queue"); if (thread) thread->packetQueue()->blockFull(false); //qDebug("try wake up audio queue"); thread = mDemuxThread->audioThread(); if (thread) thread->packetQueue()->blockFull(false); }
void AVDemuxThread::nextFrame() { // clock type will be wrong if no lock because slot frameDeliveredNextFrame() is in video thread QMutexLocker locker(&next_frame_mutex); Q_UNUSED(locker); pause(true); // must pause AVDemuxThread (set user_paused true) AVThread* av[] = {video_thread, audio_thread}; bool connected = false; for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) { AVThread *t = av[i]; if (!t) continue; // set clock first if (clock_type < 0) clock_type = (int)t->clock()->isClockAuto() + 2*(int)t->clock()->clockType(); t->clock()->setClockType(AVClock::VideoClock); t->scheduleFrameDrop(false); t->pause(false); t->packetQueue()->blockFull(false); if (!connected) { connect(t, SIGNAL(frameDelivered()), this, SLOT(frameDeliveredNextFrame()), Qt::DirectConnection); connected = true; } } emit requestClockPause(false); nb_next_frame.ref(); pauseInternal(false); }
void run() { AVThread *avt = demux_thread->videoThread(); avt->packetQueue()->clear(); // clear here if (pts <= 0) { demux_thread->demuxer->seek(qint64(-pts*1000.0) - 500LL); QVector<qreal> ts; qreal t = -1.0; while (t < -pts) { demux_thread->demuxer->readFrame(); if (demux_thread->demuxer->stream() != demux_thread->demuxer->videoStream()) continue; t = demux_thread->demuxer->packet().pts; ts.push_back(t); } const qreal t0 = ts.back(); ts.pop_back(); const qreal dt = t0 - ts.back(); pts = ts.back(); // FIXME: sometimes can not seek to the previous pts, the result pts is always current pts, so let the target pts a little earlier pts -= dt/2.0; } qDebug("step backward: %lld, %f", qint64(pts*1000.0), pts); demux_thread->video_thread->setDropFrameOnSeek(false); demux_thread->seekInternal(qint64(pts*1000.0), AccurateSeek); }
void AVDemuxThread::seekInternal(qint64 pos, SeekType type) { AVThread* av[] = { audio_thread, video_thread}; qDebug("seek to %s %lld ms (%f%%)", QTime(0, 0, 0).addMSecs(pos).toString().toUtf8().constData(), pos, double(pos - demuxer->startTime())/double(demuxer->duration())*100.0); demuxer->setSeekType(type); demuxer->seek(pos); if (ademuxer) { ademuxer->setSeekType(type); ademuxer->seek(pos); } AVThread *watch_thread = 0; // TODO: why queue may not empty? int sync_id = 0; for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) { AVThread *t = av[i]; if (!t) continue; if (!sync_id) sync_id = t->clock()->syncStart(!!audio_thread + !!video_thread); Q_ASSERT(sync_id != 0); qDebug("demuxer sync id: %d/%d", sync_id, t->clock()->syncId()); t->packetQueue()->clear(); t->requestSeek(); // TODO: the first frame (key frame) will not be decoded correctly if flush() is called. //PacketBuffer *pb = t->packetQueue(); //qDebug("%s put seek packet. %d/%d-%.3f, progress: %.3f", t->metaObject()->className(), pb->buffered(), pb->bufferValue(), pb->bufferMax(), pb->bufferProgress()); t->packetQueue()->setBlocking(false); // aqueue bufferValue can be small (1), we can not put and take Packet pkt; pkt.pts = qreal(pos)/1000.0; pkt.position = sync_id; t->packetQueue()->put(pkt); t->packetQueue()->setBlocking(true); // blockEmpty was false when eof is read. if (isPaused()) { //TODO: deal with pause in AVThread? t->pause(false); watch_thread = t; } } if (watch_thread) { pauseInternal(false); Q_EMIT requestClockPause(false); // need direct connection // direct connection is fine here connect(watch_thread, SIGNAL(seekFinished(qint64)), this, SLOT(seekOnPauseFinished()), Qt::DirectConnection); } }
//No more data to put. So stop blocking the queue to take the reset elements void AVDemuxThread::stop() { //this will not affect the pause state if we pause the output //TODO: why remove blockFull(false) can not play another file? AVThread* av[] = { audio_thread, video_thread}; for (size_t i = 0; i < sizeof(av)/sizeof(av[0]); ++i) { AVThread* t = av[i]; if (!t) continue; t->packetQueue()->clear(); t->packetQueue()->blockFull(false); //?? while (t->isRunning()) { qDebug() << "stopping thread " << t; t->stop(); t->wait(500); } } pause(false); cond.wakeAll(); qDebug("all avthread finished. try to exit demux thread<<<<<<"); end = true; }
void AVDemuxThread::run() { m_buffering = false; end = false; if (audio_thread && !audio_thread->isRunning()) audio_thread->start(QThread::HighPriority); if (video_thread && !video_thread->isRunning()) video_thread->start(); int stream = 0; Packet pkt; pause(false); qDebug("get av queue a/v thread = %p %p", audio_thread, video_thread); PacketBuffer *aqueue = audio_thread ? audio_thread->packetQueue() : 0; PacketBuffer *vqueue = video_thread ? video_thread->packetQueue() : 0; // aqueue as a primary buffer: music with/without cover AVThread* thread = !video_thread || (audio_thread && demuxer->hasAttacedPicture()) ? audio_thread : video_thread; m_buffer = thread->packetQueue(); const qint64 buf2 = aqueue ? aqueue->bufferValue() : 1; // TODO: may be changed by user. Deal with audio track change if (aqueue) { aqueue->clear(); aqueue->setBlocking(true); } if (vqueue) { vqueue->clear(); vqueue->setBlocking(true); } connect(thread, SIGNAL(seekFinished(qint64)), this, SIGNAL(seekFinished(qint64)), Qt::DirectConnection); seek_tasks.clear(); bool was_end = false; if (ademuxer) { ademuxer->seek(0LL); } while (!end) { processNextSeekTask(); //vthread maybe changed by AVPlayer.setPriority() from no dec case vqueue = video_thread ? video_thread->packetQueue() : 0; if (demuxer->atEnd()) { // if avthread may skip 1st eof packet because of a/v sync if (aqueue && (!was_end || aqueue->isEmpty())) { aqueue->put(Packet::createEOF()); aqueue->blockEmpty(false); // do not block if buffer is not enough. block again on seek } if (vqueue && (!was_end || vqueue->isEmpty())) { vqueue->put(Packet::createEOF()); vqueue->blockEmpty(false); } m_buffering = false; Q_EMIT mediaStatusChanged(QtAV::BufferedMedia); was_end = true; // wait for a/v thread finished msleep(100); continue; } was_end = false; if (tryPause()) { continue; //the queue is empty and will block } updateBufferState(); if (!demuxer->readFrame()) { continue; } stream = demuxer->stream(); pkt = demuxer->packet(); Packet apkt; bool audio_has_pic = demuxer->hasAttacedPicture(); int a_ext = 0; if (ademuxer) { QMutexLocker locker(&buffer_mutex); Q_UNUSED(locker); if (ademuxer) { a_ext = -1; audio_has_pic = ademuxer->hasAttacedPicture(); // FIXME: buffer full but buffering!!! // avoid read external track everytime. aqueue may not block full // vqueue will not block if aqueue is not enough if (!aqueue->isFull() || aqueue->isBuffering()) { if (ademuxer->readFrame()) { if (ademuxer->stream() == ademuxer->audioStream()) { a_ext = 1; apkt = ademuxer->packet(); } } // no continue otherwise. ademuxer finished earlier than demuxer } } } //qDebug("vqueue: %d, aqueue: %d/isbuffering %d isfull: %d, buffer: %d/%d", vqueue->size(), aqueue->size(), aqueue->isBuffering(), aqueue->isFull(), aqueue->buffered(), aqueue->bufferValue()); //QMutexLocker locker(&buffer_mutex); //TODO: seems we do not need to lock //Q_UNUSED(locker); /*1 is empty but another is enough, then do not block to ensure the empty one can put packets immediatly. But usually it will not happen, why? */ /* demux thread will be blocked only when 1 queue is full and still put * if vqueue is full and aqueue becomes empty, then demux thread * will be blocked. so we should wake up another queue when empty(or threshold?). * TODO: the video stream and audio stream may be group by group. provide it * stream data: aaaaaaavvvvvvvaaaaaaaavvvvvvvvvaaaaaa, it happens * stream data: aavavvavvavavavavavavavavvvaavavavava, it's ok */ //TODO: use cache queue, take from cache queue if not empty? const bool a_internal = stream == demuxer->audioStream(); if (a_internal || a_ext > 0) {//apkt.isValid()) { if (a_internal && !a_ext) // internal is always read even if external audio used apkt = demuxer->packet(); /* if vqueue if not blocked and full, and aqueue is empty, then put to * vqueue will block demuex thread */ if (aqueue) { if (!audio_thread || !audio_thread->isRunning()) { aqueue->clear(); continue; } // must ensure bufferValue set correctly before continue if (m_buffer != aqueue) aqueue->setBufferValue(m_buffer->isBuffering() ? std::numeric_limits<qint64>::max() : buf2); // always block full if no vqueue because empty callback may set false // attached picture is cover for song, 1 frame aqueue->blockFull(!video_thread || !video_thread->isRunning() || !vqueue || audio_has_pic); // external audio: a_ext < 0, stream = audio_idx=>put invalid packet if (a_ext >= 0) aqueue->put(apkt); //affect video_thread } } // always check video stream if use external audio if (stream == demuxer->videoStream()) { if (vqueue) { if (!video_thread || !video_thread->isRunning()) { vqueue->clear(); continue; } vqueue->blockFull(!audio_thread || !audio_thread->isRunning() || !aqueue || aqueue->isEnough()); vqueue->put(pkt); //affect audio_thread } } else if (demuxer->subtitleStreams().contains(stream)) { //subtitle Q_EMIT internalSubtitlePacketRead(demuxer->subtitleStreams().indexOf(stream), pkt); } else { continue; } } m_buffering = false; m_buffer = 0; while (audio_thread && audio_thread->isRunning()) { qDebug("waiting audio thread......."); aqueue->blockEmpty(false); //FIXME: why need this audio_thread->wait(500); } while (video_thread && video_thread->isRunning()) { qDebug("waiting video thread......."); vqueue->blockEmpty(false); video_thread->wait(500); } thread->disconnect(this, SIGNAL(seekFinished(qint64))); qDebug("Demux thread stops running...."); emit mediaStatusChanged(QtAV::EndOfMedia); }