/****************************************************************************** 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 "AVPlayerPrivate.h" #include #include #include #include #include #include #include #include "AVDemuxer.h" #include "Packet.h" #include "AudioDecoder.h" #include "MediaIO.h" #include "VideoRenderer.h" #include "AVClock.h" #include "VideoCapture.h" #include "VideoCapture.h" #include "FilterManager.h" #include "OutputSet.h" #include "AudioThread.h" #include "VideoThread.h" #include "AVDemuxThread.h" #include "AVCompat.h" #include "internal.h" #include "Logger.h" #include "fav_common.h" #if (LOADING_PERFORMANCE_TEST || FILE_LOADING_TIMELAPSE) #include QElapsedTimer gPlayerTimer; #endif #define EOF_ISSUE_SOLVED 0 namespace FAV { namespace { static const struct RegisterMetaTypes { inline RegisterMetaTypes() { qRegisterMetaType(); // required by invoke() parameters } } _registerMetaTypes; } //namespace static const qint64 kSeekMS = 10000; Q_GLOBAL_STATIC(QThreadPool, loaderThreadPool) /// Supported input protocols. A static string list const QStringList& AVPlayer::supportedProtocols() { return AVDemuxer::supportedProtocols(); } #if (USE_FFMPEG_PW) void AVPlayer::setPassword(char* pw) { avformat_set_telebit_password(pw,strlen(pw)); } char* AVPlayer::getPassword() { int l = 0; return avformat_get_telebit_password(&l); } #endif // USE_FFMPEG_PW AVPlayer::AVPlayer(QObject *parent) : QObject(parent) , d(new Private()) { d->vos = new OutputSet(this); d->aos = new OutputSet(this); connect(this, SIGNAL(started()), this, SLOT(onStarted())); /* * call stop() before the window(d->vo) closed to stop the waitcondition * If close the d->vo widget, the the d->vo may destroy before waking up. */ connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuitApp())); //d->clock->setClockType(AVClock::ExternalClock); connect(&d->demuxer, SIGNAL(started()), masterClock(), SLOT(start())); connect(&d->demuxer, SIGNAL(error(FAV::AVError)), this, SIGNAL(error(FAV::AVError))); connect(&d->demuxer, SIGNAL(mediaStatusChanged(FAV::MediaStatus)), this, SLOT(updateMediaStatus(FAV::MediaStatus)), Qt::DirectConnection); connect(&d->demuxer, SIGNAL(loaded()), this, SIGNAL(loaded())); connect(&d->demuxer, SIGNAL(seekableChanged()), this, SIGNAL(seekableChanged())); d->read_thread = new AVDemuxThread(this); d->read_thread->setDemuxer(&d->demuxer); //direct connection can not sure slot order? connect(d->read_thread, SIGNAL(finished()), this, SLOT(stopFromDemuxerThread()), Qt::DirectConnection); connect(d->read_thread, SIGNAL(requestClockPause(bool)), masterClock(), SLOT(pause(bool)), Qt::DirectConnection); connect(d->read_thread, SIGNAL(mediaStatusChanged(FAV::MediaStatus)), this, SLOT(updateMediaStatus(FAV::MediaStatus))); connect(d->read_thread, SIGNAL(bufferProgressChanged(qreal)), this, SIGNAL(bufferProgressChanged(qreal))); #if (PLAY_SYNC_FIX2) connect(d->read_thread, SIGNAL(seekFinished(qint64,qint64)), this, SLOT(onSeekFinished(qint64,qint64)), Qt::DirectConnection); // #else connect(d->read_thread, SIGNAL(seekFinished(qint64)), this, SLOT(onSeekFinished(qint64)), Qt::DirectConnection); // #endif #if !(DO_NOT_USE_SUBTITLE) connect(d->read_thread, SIGNAL(internalSubtitlePacketRead(int, FAV::Packet)), this, SIGNAL(internalSubtitlePacketRead(int, FAV::Packet)), Qt::DirectConnection); #endif d->vcapture = new VideoCapture(this); playerID = -1; } void AVPlayer::setPlayerID(int id) { d->demuxer.playerID = id; playerID = id; d->read_thread->playerID = id; } AVPlayer::~AVPlayer() { stop(); QMutexLocker lock(&d->load_mutex); Q_UNUSED(lock); // if not uninstall here, player's qobject children filters will call uninstallFilter too late that player is almost be destroyed QList filters(FilterManager::instance().videoFilters(this)); foreach (Filter* f, filters) { uninstallFilter(reinterpret_cast(f)); } filters = FilterManager::instance().audioFilters(this); foreach (Filter* f, filters) { uninstallFilter(reinterpret_cast(f)); } } AVClock* AVPlayer::masterClock() { return d->clock; } QSize AVPlayer::videoResolution() const { AVCodecContext* c = d->demuxer.videoCodecContext(d->demuxer.videoStream()); if(c != NULL) { return QSize(c->width,c->height); } return QSize(0,0); } void AVPlayer::addVideoRenderer(VideoRenderer *renderer) { if (!renderer) { qWarning("add a null renderer!"); return; } renderer->setStatistics(&d->statistics); d->vos->addOutput(renderer); } void AVPlayer::removeVideoRenderer(VideoRenderer *renderer) { d->vos->removeOutput(renderer); } void AVPlayer::clearVideoRenderers() { d->vos->clearOutputs(); } void AVPlayer::setRenderer(VideoRenderer *r, bool updateCustomRatio) { VideoRenderer *vo = renderer(); if (vo && r) { VideoRenderer::OutAspectRatioMode oar = vo->outAspectRatioMode(); //r->resizeRenderer(vo->rendererSize()); r->setOutAspectRatioMode(oar); if (updateCustomRatio && oar == VideoRenderer::CustomAspectRation) { r->setOutAspectRatio(vo->outAspectRatio()); } } clearVideoRenderers(); if (!r) return; r->resizeRenderer(r->rendererSize()); //IMPORTANT: the swscaler will resize r->setStatistics(&d->statistics); addVideoRenderer(r); } VideoRenderer *AVPlayer::renderer() { //QList assert empty in debug mode if (!d->vos || d->vos->outputs().isEmpty()) return 0; return static_cast(d->vos->outputs().last()); } QList AVPlayer::videoOutputs() { if (!d->vos) return QList(); QList vos; vos.reserve(d->vos->outputs().size()); foreach (AVOutput *out, d->vos->outputs()) { vos.append(static_cast(out)); } return vos; } AudioOutput* AVPlayer::audio() { return d->ao; } void AVPlayer::setSpeed(qreal speed) { if (speed == d->speed) return; // SPEED CHANGE ERROR!!!! #if !(FIX_SPEED_CHANGE_ERROR) setFrameRate(0); // will set clock to default #endif d->speed = speed; //TODO: check clock type? if (d->ao && d->ao->isAvailable()) { #if !(OFF_OTHER_DEBUG) qDebug("set speed %.2f", d->speed); #endif d->ao->setSpeed(d->speed); } masterClock()->setSpeed(d->speed); Q_EMIT speedChanged(d->speed); } qreal AVPlayer::speed() const { return d->speed; } void AVPlayer::setInterruptTimeout(qint64 ms) { if (ms < 0LL) ms = -1LL; if (d->interrupt_timeout == ms) return; d->interrupt_timeout = ms; Q_EMIT interruptTimeoutChanged(); d->demuxer.setInterruptTimeout(ms); } qint64 AVPlayer::interruptTimeout() const { return d->interrupt_timeout; } void AVPlayer::setInterruptOnTimeout(bool value) { if (isInterruptOnTimeout() == value) return; d->demuxer.setInterruptOnTimeout(value); Q_EMIT interruptOnTimeoutChanged(); } bool AVPlayer::isInterruptOnTimeout() const { return d->demuxer.isInterruptOnTimeout(); } void AVPlayer::setFrameRate(qreal value) { d->force_fps = value; // clock set here will be reset in playInternal() // also we can't change user's setting of ClockType and autoClock here if force frame rate is disabled. if (!isPlaying()) { return; } d->applyFrameRate(); } qreal AVPlayer::forcedFrameRate() const { return d->force_fps; } const Statistics& AVPlayer::statistics() const { return d->statistics; } bool AVPlayer::installFilter(AudioFilter *filter, int index) { if (!FilterManager::instance().registerAudioFilter((Filter*)filter, this, index)) return false; if (!d->athread) return false; //install later when avthread created return d->athread->installFilter((Filter*)filter, index); } bool AVPlayer::installFilter(VideoFilter *filter, int index) { if (!FilterManager::instance().registerVideoFilter((Filter*)filter, this, index)) return false; if (!d->vthread) return false; //install later when avthread created return d->vthread->installFilter((Filter*)filter, index); } bool AVPlayer::uninstallFilter(AudioFilter *filter) { FilterManager::instance().unregisterAudioFilter(filter, this); AVThread *avthread = d->athread; if (!avthread) return false; if (!avthread->filters().contains(filter)) return false; return avthread->uninstallFilter(filter, true); } bool AVPlayer::uninstallFilter(VideoFilter *filter) { FilterManager::instance().unregisterVideoFilter(filter, this); AVThread *avthread = d->vthread; if (!avthread) return false; if (!avthread->filters().contains(filter)) return false; return avthread->uninstallFilter(filter, true); } QList AVPlayer::audioFilters() const { return FilterManager::instance().audioFilters((AVPlayer*)this); } QList AVPlayer::videoFilters() const { return FilterManager::instance().videoFilters((AVPlayer*)this); } void AVPlayer::setPriority(const QVector &ids) { d->vc_ids = ids; if (!isPlaying()) return; // TODO: add an option to apply immediatly? if (!d->vthread || !d->vthread->isRunning()) { qint64 pos = position(); d->setupVideoThread(this); if (d->vdec) { d->vthread->start(); setPosition(pos); } return; } #ifndef ASYNC_DECODER_OPEN class ChangeDecoderTask : public QRunnable { AVPlayer* player; public: ChangeDecoderTask(AVPlayer *p) : player(p) {} void run() Q_DECL_OVERRIDE { player->d->tryApplyDecoderPriority(player); } }; d->vthread->scheduleTask(new ChangeDecoderTask(this)); #else // maybe better experience class NewDecoderTask : public QRunnable { AVPlayer *player; public: NewDecoderTask(AVPlayer *p) : player(p) {} void run() Q_DECL_OVERRIDE { VideoDecoder *vd = NULL; AVCodecContext *avctx = player->d->demuxer.videoCodecContext(); foreach(VideoDecoderId vid, player->d->vc_ids) { qDebug("**********trying video decoder: %s...", VideoDecoderFactory::name(vid).c_str()); vd = VideoDecoder::create(vid); if (!vd) continue; vd->setCodecContext(avctx); // It's fine because AVDecoder copy the avctx properties vd->setOptions(player->d->vc_opt); if (vd->open()) { qDebug("**************Video decoder found:%p", vd); break; } delete vd; vd = 0; } if (!vd) { Q_EMIT player->error(AVError(AVError::VideoCodecNotFound)); return; } if (vd->id() == player->d->vdec->id()) { qDebug("Video decoder does not change"); delete vd; return; } class ApplyNewDecoderTask : public QRunnable { AVPlayer *player; VideoDecoder *dec; public: ApplyNewDecoderTask(AVPlayer *p, VideoDecoder *d) : player(p), dec(d) {} void run() Q_DECL_OVERRIDE { qint64 pos = player->position(); VideoThread *vthread = player->d->vthread; vthread->packetQueue()->clear(); vthread->setDecoder(dec); // MUST delete decoder after video thread set the decoder to ensure the deleted vdec will not be used in vthread! if (player->d->vdec) delete player->d->vdec; player->d->vdec = dec; QObject::connect(player->d->vdec, SIGNAL(error(FAV::AVError)), player, SIGNAL(error(FAV::AVError))); player->d->initVideoStatistics(player->d->demuxer.videoStream()); // If no seek, drop packets until a key frame packet is found. But we may drop too many packets, and also a/v sync is a problem. player->setPosition(pos); } }; player->d->vthread->scheduleTask(new ApplyNewDecoderTask(player, vd)); } }; QThreadPool::globalInstance()->start(new NewDecoderTask(this),LOADER_THREAD_PRIORITY); #endif } template static QVector idsFromNames(const QStringList& names) { QVector decs; if (!names.isEmpty()) { decs.reserve(names.size()); foreach (const QString& name, names) { if (name.isEmpty()) continue; ID id = T::id(name.toLatin1().constData()); if (id == 0) continue; decs.append(id); } } return decs; } void AVPlayer::setVideoDecoderPriority(const QStringList &names) { setPriority(idsFromNames(names)); } template static QStringList idsToNames(QVector ids) { QStringList decs; if (!ids.isEmpty()) { decs.reserve(ids.size()); foreach (ID id, ids) { decs.append(QString::fromLatin1(T::name(id))); } } return decs; } QStringList AVPlayer::videoDecoderPriority() const { return idsToNames(d->vc_ids); } void AVPlayer::setOptionsForFormat(const QVariantHash &dict) { d->demuxer.setOptions(dict); } QVariantHash AVPlayer::optionsForFormat() const { return d->demuxer.options(); } void AVPlayer::setOptionsForAudioCodec(const QVariantHash &dict) { d->ac_opt = dict; } QVariantHash AVPlayer::optionsForAudioCodec() const { return d->ac_opt; } void AVPlayer::setOptionsForVideoCodec(const QVariantHash &dict) { d->vc_opt = dict; const QVariant p(dict.contains(QStringLiteral("priority"))); if (p.type() == QVariant::StringList) { setVideoDecoderPriority(p.toStringList()); d->vc_opt.remove(QStringLiteral("priority")); } } QVariantHash AVPlayer::optionsForVideoCodec() const { return d->vc_opt; } void AVPlayer::setMediaEndAction(MediaEndAction value) { if (d->end_action == value) return; d->end_action = value; Q_EMIT mediaEndActionChanged(value); d->read_thread->setMediaEndAction(value); } MediaEndAction AVPlayer::mediaEndAction() const { return d->end_action; } /* * loaded state is the state of current setted file. * For replaying, we can avoid load a seekable file again. * For playing a new file, load() is required. */ void AVPlayer::setFile(const QString &path) { // file() is used somewhere else. ensure it is correct QString p(path); // QFile does not support "file:" if (p.startsWith(QLatin1String("file:"))) { p = Internal::Path::toLocal(p); } d->reset_state = d->current_source.type() != QVariant::String || d->current_source.toString() != p; d->current_source = p; // TODO: d->reset_state = d->demuxer2.setMedia(path); if (d->reset_state) { #if (DO_NOT_USE_SUBTITLE) d->audio_track = d->video_track = 0; #else d->audio_track = d->video_track = d->subtitle_track = 0; #endif Q_EMIT sourceChanged(); //Q_EMIT error(AVError(AVError::NoError)); } // TODO: use absoluteFilePath? d->loaded = false; // } QString AVPlayer::file() const { if (d->current_source.type() == QVariant::String) return d->current_source.toString(); return QString(); } void AVPlayer::setIODevice(QIODevice* device) { // TODO: d->reset_state = d->demuxer2.setMedia(device); if (d->current_source.type() == QVariant::String) { d->reset_state = true; } else { if (d->current_source.canConvert()) { d->reset_state = d->current_source.value() != device; } else { // MediaIO d->reset_state = true; } } d->loaded = false; d->current_source = QVariant::fromValue(device); if (d->reset_state) { #if (DO_NOT_USE_SUBTITLE) d->audio_track = d->video_track; #else d->audio_track = d->video_track = d->subtitle_track = 0; #endif Q_EMIT sourceChanged(); } } void AVPlayer::setInput(MediaIO *in) { // TODO: d->reset_state = d->demuxer2.setMedia(in); if (d->current_source.type() == QVariant::String) { d->reset_state = true; } else { if (d->current_source.canConvert()) { d->reset_state = true; } else { // MediaIO d->reset_state = d->current_source.value() != in; } } d->loaded = false; d->current_source = QVariant::fromValue(in); if (d->reset_state) { #if (DO_NOT_USE_SUBTITLE) d->audio_track = d->video_track; #else d->audio_track = d->video_track = d->subtitle_track = 0; #endif Q_EMIT sourceChanged(); } } MediaIO* AVPlayer::input() const { if (d->current_source.type() == QVariant::String) return 0; if (!d->current_source.canConvert()) return 0; return d->current_source.value(); } VideoCapture* AVPlayer::videoCapture() const { return d->vcapture; } void AVPlayer::play(const QString& path) { setFile(path); play(); } bool AVPlayer::isPlaying() const { return (d->read_thread &&d->read_thread->isRunning()) || (d->athread && d->athread->isRunning()) || (d->vthread && d->vthread->isRunning()); } void AVPlayer::togglePause() { pause(!isPaused()); } void AVPlayer::pause(bool p) { if (!isPlaying()) { qInfo() << "pause fail isPlaying" << __FUNCTION__ << __LINE__; return; } if (isPaused() == p) { qInfo() << "pause fail is already paused" << __FUNCTION__ << __LINE__; return; } audio()->pause(p); //pause thread. check pause state? d->read_thread->pause(p); if (d->athread) { d->athread->pause(p); } if (d->vthread) { //PLAYER_DEBUG("****** IS MY PAUSE",100) d->vthread->pause(p); } d->clock->pause(p); d->state = p ? PausedState : PlayingState; Q_EMIT stateChanged(d->state); // 외부.. Q_EMIT paused(p); //qInfo() << "%%%% PAUSED:" << _playerID << "ON/OFF:" << p << __FUNCTION__; } bool AVPlayer::isPaused() const { // if(_playerID == 1) // { // qInfo() << "d->athread" << d->athread << (d->athread != NULL && d->athread->isPaused()); // qInfo() << "d->vthread" << d->vthread << (d->vthread != NULL && d->vthread->isPaused()); // qInfo() << "d->read_thread" << d->read_thread << (d->read_thread != NULL && d->read_thread->isPaused()); // } return (d->read_thread && d->read_thread->isPaused()) || (d->athread && d->athread->isPaused()) || (d->vthread && d->vthread->isPaused()); } #if (FIXED_FPS_DURATION) void AVPlayer::setFixedDuration(quint64 duration) { #if (REAR_SYNC_FRONT) if(true)//_playerID == 1) { applyRearDuration(duration * 1000); } #else d->demuxer.durationFixed = duration * 1000; #endif } #endif MediaStatus AVPlayer::mediaStatus() const { return d->status; } void AVPlayer::setAutoLoad(bool value) { if (d->auto_load == value) return; d->auto_load = value; Q_EMIT autoLoadChanged(); } bool AVPlayer::isAutoLoad() const { return d->auto_load; } void AVPlayer::setAsyncLoad(bool value) { if (d->async_load == value) return; d->async_load = value; Q_EMIT asyncLoadChanged(); } bool AVPlayer::isAsyncLoad() const { return d->async_load; } bool AVPlayer::isLoaded() const { return d->loaded; } #if (REAR_SYNC_FRONT) void AVPlayer::applyRearDuration(qint64 frontDuraiton) { //qInfo() << __FUNCTION__ << frontDuraiton << " ? " << d->demuxer.durationFixed; if(d->demuxer.playerID == 1) { // 항상 후방이 더 길게 설정됨 //qInfo() << "R:" << d->demuxer.durationFixed << "F:" << frontDuraiton << " DIF:" << frontDuraiton - d->demuxer.durationFixed; // 후방이 전방보다 짧을 경우 if(d->demuxer.durationFixed < frontDuraiton) { //qInfo() << " df:" << d->demuxer.durationFixed << " da:" << d->demuxer.durationUSAudio(); //qInfo() << __FUNCTION__ << "DELAY REAR" << "f:" << frontDuraiton << " r:" << d->demuxer.durationFixed << " = " << (double)(frontDuraiton - d->demuxer.durationFixed) / 1000000.0; //d->demuxer.durationFixed = frontDuraiton-1000000; d->demuxer.rear_delay = (double)(frontDuraiton - d->demuxer.durationFixed) / 1000000.0; } else if (d->demuxer.durationFixed > frontDuraiton) { d->demuxer.rear_delay = 0.0; d->demuxer.durationFixed = frontDuraiton; } else { d->demuxer.rear_delay = 0.0; } d->read_thread->rear_delay = d->demuxer.rear_delay; } else { d->demuxer.rear_delay = 0.0; d->read_thread->rear_delay = 0.0; } #if (PLAY_SYNC_FIX2) d->media_end = ((d->demuxer.durationFixed - (d->demuxer.frameDuration * 1000)) / 1000); //qInfo() << "%%% DEMUXER MEDIA END:" << d->media_end << __FUNCTION__; #else d->media_end = d->demuxer.durationFixed / 1000; #endif d->stop_position_norm = d->media_end; // SEEK 안됨 } #endif void AVPlayer::loadInternal() { QMutexLocker lock(&d->load_mutex); Q_UNUSED(lock); // release codec ctx //close decoders here to make sure open and close in the same thread if not async load if (isLoaded()) { if (d->adec) // audio decoder { d->adec->setCodecContext(0); } if (d->vdec) // audio decoder { d->vdec->setCodecContext(0); } } qDebug() << "Loading " << d->current_source << " ..."; if (d->current_source.type() == QVariant::String) { d->demuxer.setMedia(d->current_source.toString()); } else { if (d->current_source.canConvert()) { d->demuxer.setMedia(d->current_source.value()); } else { // MediaIO d->demuxer.setMedia(d->current_source.value()); } } // 로딩에서 시간 가장 많이 걸리는 부분 d->loaded = d->demuxer.load(); d->status = d->demuxer.mediaStatus(); #if (FORCE_FPS_PLAYER) AVCodecContext* vctx = d->demuxer.videoCodecContext(); setFrameRate(vctx->framerate.num / vctx->framerate.den); //qInfo() << "SET FRAME RATE:" << (vctx->framerate.num / vctx->framerate.den); #endif if (!d->loaded) { d->statistics.reset(); qWarning("Load failed!"); d->audio_tracks = d->getTracksInfo(&d->demuxer, AVDemuxer::AudioStream); Q_EMIT internalAudioTracksChanged(d->audio_tracks); // d->subtitle_tracks = d->getTracksInfo(&d->demuxer, AVDemuxer::SubtitleStream); // Q_EMIT internalSubtitleTracksChanged(d->subtitle_tracks); return; } // d->subtitle_tracks = d->getTracksInfo(&d->demuxer, AVDemuxer::SubtitleStream); // Q_EMIT internalSubtitleTracksChanged(d->subtitle_tracks); // d->applySubtitleStream(d->subtitle_track, this); d->audio_tracks = d->getTracksInfo(&d->demuxer, AVDemuxer::AudioStream); Q_EMIT internalAudioTracksChanged(d->audio_tracks); Q_EMIT durationChanged(duration()); // setup parameters from loaded media d->media_start_pts = d->demuxer.startTime(); // TODO: what about other proctols? some vob duration() == 0 if (duration() > 0) { d->media_end = mediaStartPosition() + duration(); } else { d->media_end = kInvalidPosition; } d->start_position_norm = normalizedPosition(d->start_position); d->stop_position_norm = normalizedPosition(d->stop_position); int interval = qAbs(d->notify_interval); d->initStatistics(); if (interval != qAbs(d->notify_interval)) { Q_EMIT notifyIntervalChanged(); } } void AVPlayer::unload() { if (!isLoaded()) return; QMutexLocker lock(&d->load_mutex); Q_UNUSED(lock); d->loaded = false; d->demuxer.setInterruptStatus(-1); if (d->adec) { // FIXME: crash if audio external=>internal then replay d->adec->setCodecContext(0); delete d->adec; d->adec = 0; } if (d->vdec) { d->vdec->setCodecContext(0); delete d->vdec; d->vdec = 0; } d->demuxer.unload(); Q_EMIT durationChanged(0LL); // for ui, slider is invalid. use stopped instead, and remove this signal here? // ?? d->audio_tracks = d->getTracksInfo(&d->demuxer, AVDemuxer::AudioStream); Q_EMIT internalAudioTracksChanged(d->audio_tracks); } void AVPlayer::setRelativeTimeMode(bool value) { if (d->relative_time_mode == value) return; d->relative_time_mode = value; Q_EMIT relativeTimeModeChanged(); } bool AVPlayer::relativeTimeMode() const { return d->relative_time_mode; } qint64 AVPlayer::absoluteMediaStartPosition() const { return d->media_start_pts; } qreal AVPlayer::durationF() const { return double(d->demuxer.durationUs())/double(AV_TIME_BASE); //AVFrameContext.duration time base: AV_TIME_BASE } #if (PLAY_SYNC_FIX2) qint64 AVPlayer::frameDuration() const { return d->demuxer.frameDuration; } #endif // PLAY_SYNC_FIX2 qint64 AVPlayer::duration() const { // QMutexLocker lock(&d->load_mutex); // Q_UNUSED(lock); return d->demuxer.duration(); } qint64 AVPlayer::mediaStartPosition() const { if (relativeTimeMode()) return 0; return d->demuxer.startTime(); } qint64 AVPlayer::mediaStopPosition() const { if (d->media_end == kInvalidPosition && duration() > 0) { // called in stop() return mediaStartPosition() + duration(); } return d->media_end; } qreal AVPlayer::mediaStartPositionF() const { if (relativeTimeMode()) return 0; return double(d->demuxer.startTimeUs())/double(AV_TIME_BASE); } qint64 AVPlayer::normalizedPosition(qint64 pos) { if (!isLoaded()) return pos; qint64 p0 = mediaStartPosition(); qint64 p1 = mediaStopPosition(); if (relativeTimeMode()) { p0 = 0; if (p1 != kInvalidPosition) p1 -= p0; //duration } if (pos < 0) { if (p1 == kInvalidPosition) pos = kInvalidPosition; else pos += p1; } return qMax(qMin(pos, p1), p0); } qint64 AVPlayer::startPosition() const { return d->start_position; } void AVPlayer::setStartPosition(qint64 pos) { d->start_position = pos; d->start_position_norm = normalizedPosition(pos); Q_EMIT startPositionChanged(d->start_position); } qint64 AVPlayer::stopPosition() const { return d->stop_position; } void AVPlayer::setStopPosition(qint64 pos) { d->stop_position = pos; d->stop_position_norm = normalizedPosition(pos); Q_EMIT stopPositionChanged(d->stop_position); } void AVPlayer::setTimeRange(qint64 start, qint64 stop) { if (start > stop) { qWarning("Invalid time range"); return; } setStopPosition(stop); setStartPosition(start); } bool AVPlayer::isSeekable() const { return d->demuxer.isSeekable(); } bool AVPlayer::seek_exist() { return d->read_thread->seek_exist(); } qint64 AVPlayer::position() const { // TODO: videoTime()? const qint64 pts = d->clock->value()*1000.0; //qInfo() << playerID << pts << __LINE__ << __FUNCTION__; if (relativeTimeMode()) return pts - absoluteMediaStartPosition(); return pts; } void AVPlayer::setPosition(qint64 position) { PLAYER_DEBUG_V("$$$SET_POS",50,position); //qInfo() << __FUNCTION__ << position << seekType(); //position = qMin(position,d->demuxer.duration()-100); //qInfo() << __FUNCTION__ << " id:" << _playerID << " pos:" << position << " d->stop_position_norm:" << d->stop_position_norm; // FIXME: strange things happen if seek out of eof //qInfo() << "d->stop_position_norm::" << d->stop_position_norm; #if !(PLAY_SYNC_FIX2) if (d->stop_position_norm >= 0 && position >= d->stop_position_norm) { position = d->stop_position_norm - 10; } #endif // PLAY_SYNC_FIX2 if (!isPlaying()) { qInfo() << __FUNCTION__ << playerID << "is not playing return"; return; } qint64 pos_pts = position; if (pos_pts < 0) pos_pts = 0; // position passed in is relative to the start pts in relative time mode if (relativeTimeMode()) { pos_pts += absoluteMediaStartPosition(); } d->seeking = true; // seek 종료되어 동기화 대기중인 상태에서 clock 업데이트 하면 문제 발생 //qInfo() << "T1:" << pos_pts; /* masterClock()->updateValue(double(pos_pts)/1000.0); //what is duration == 0 if (d->vthread->do_not_update_next_clock == true) { //qInfo() << "do_not_update_next_clock skip..."; } else { masterClock()->updateExternalClock(pos_pts); //in msec. ignore usec part using t/1000 } */ // if(_playerID == 1) // { // qInfo() << __FUNCTION__ << _playerID << " pos:" << pos_pts; // } d->read_thread->seek(pos_pts, seekType()); Q_EMIT positionChanged(position); //emit relative position } int AVPlayer::repeat() const { return d->repeat_max; } int AVPlayer::currentRepeat() const { return d->repeat_current; } // TODO: reset current_repeat? void AVPlayer::setRepeat(int max) { d->repeat_max = max; if (d->repeat_max < 0) d->repeat_max = std::numeric_limits::max(); Q_EMIT repeatChanged(d->repeat_max); } bool AVPlayer::setExternalAudio(const QString &file) { // TODO: update statistics int stream = currentAudioStream(); if (!isLoaded() && stream < 0) stream = 0; return setAudioStream(file, stream); } QString AVPlayer::externalAudio() const { return d->external_audio; } const QVariantList& AVPlayer::externalAudioTracks() const { return d->external_audio_tracks; } const QVariantList &AVPlayer::internalAudioTracks() const { return d->audio_tracks; } bool AVPlayer::setAudioStream(const QString &file, int n) { QString path(file); // QFile does not support "file:" if (path.startsWith(QLatin1String("file:"))) path = Internal::Path::toLocal(path); if (d->audio_track == n && d->external_audio == path) return true; const bool audio_changed = d->audio_demuxer.fileName() != path; if (path.isEmpty()) { if (isLoaded()) { if (n >= d->demuxer.audioStreams().size()) { qWarning("Invalid audio stream number %d/%d", n, d->demuxer.audioStreams().size()-1); return false; } } if (audio_changed) { d->external_audio_tracks = QVariantList(); Q_EMIT externalAudioTracksChanged(d->external_audio_tracks); } } else { if (!audio_changed && d->audio_demuxer.isLoaded()) { if (n >= d->audio_demuxer.audioStreams().size()) { qWarning("Invalid external audio stream number %d/%d", n, d->audio_demuxer.audioStreams().size()-1); return false; } } } d->audio_track = n; d->external_audio = path; d->audio_demuxer.setMedia(d->external_audio); struct scoped_pause { scoped_pause() : was_paused(false), player(0) {} void set(bool old, AVPlayer* p) { was_paused = old; player = p; if (player) player->pause(true); } ~scoped_pause() { if (player && !was_paused) { player->pause(false); } } bool was_paused; AVPlayer* player; }; scoped_pause sp; if (!isPlaying()) { qDebug("set audio track when not playing"); goto update_demuxer; } // pause demuxer, clear queues, set demuxer stream, set decoder, set ao, resume sp.set(isPaused(), this); //before read_thread->pause(true, true) if (!d->external_audio.isEmpty()) d->read_thread->pause(true, true); // wait to safe set ademuxer update_demuxer: if (!d->external_audio.isEmpty()) { if (audio_changed || !d->audio_demuxer.isLoaded()) { if (!d->audio_demuxer.load()) { qWarning("Failed to load audio track %d@%s", d->audio_track, d->external_audio.toUtf8().constData()); d->external_audio_tracks = QVariantList(); Q_EMIT externalAudioTracksChanged(d->external_audio_tracks); return false; } d->external_audio_tracks = d->getTracksInfo(&d->audio_demuxer, AVDemuxer::AudioStream); Q_EMIT externalAudioTracksChanged(d->external_audio_tracks); d->read_thread->setAudioDemuxer(&d->audio_demuxer); } } if (!isPlaying()) { if (d->external_audio.isEmpty()) { if (audio_changed) { d->read_thread->setAudioDemuxer(0); d->audio_demuxer.unload(); } } return true; } if (!d->setupAudioThread(this)) { // adec will be deleted. so audio_demuxer must unload later stop(); return false; } if (d->external_audio.isEmpty()) { if (audio_changed) { d->read_thread->setAudioDemuxer(0); d->audio_demuxer.unload(); } } else { d->audio_demuxer.seek(position()); } return true; } bool AVPlayer::setAudioStream(int n) { return setAudioStream(externalAudio(), n); } qint32 AVPlayer::realVideoStreamCount() { return d->demuxer.realVideoStreamCount; } bool AVPlayer::setVideoStream(int n) { if (n < 0) return false; if (d->video_track == n) return true; if (isLoaded()) { if (n >= d->demuxer.videoStreams().size()) return false; } d->video_track = n; if (!isPlaying()) { return true; } // pause demuxer, clear queues, set demuxer stream, set decoder, set ao, resume bool p = isPaused(); pause(true); if (!d->setupVideoThread(this)) { stop(); return false; } if (!p) { pause(false); } return true; } #if !(DO_NOT_USE_SUBTITLE) const QVariantList& AVPlayer::internalSubtitleTracks() const { return d->subtitle_tracks; } bool AVPlayer::setSubtitleStream(int n) { if (d->subtitle_track == n) return true; d->subtitle_track = n; Q_EMIT subtitleStreamChanged(n); if (!d->demuxer.isLoaded()) return true; return d->applySubtitleStream(n, this); } #endif int AVPlayer::currentAudioStream() const { return d->demuxer.audioStreams().indexOf(d->demuxer.audioStream()); } int AVPlayer::currentVideoStream() const { return d->demuxer.videoStreams().indexOf(d->demuxer.videoStream()); } #if !(DO_NOT_USE_SUBTITLE) int AVPlayer::currentSubtitleStream() const { return d->demuxer.subtitleStreams().indexOf(d->demuxer.subtitleStream()); } #endif int AVPlayer::audioStreamCount() const { return d->demuxer.audioStreams().size(); } int AVPlayer::videoStreamCount() const { return d->demuxer.videoStreams().size(); } #if !(DO_NOT_USE_SUBTITLE) int AVPlayer::subtitleStreamCount() const { return d->demuxer.subtitleStreams().size(); } #endif AVPlayer::State AVPlayer::state() const { return d->state; } void AVPlayer::setState(State value) { if (d->state == value) return; if (value == StoppedState) { stop(); return; } if (value == PausedState) { pause(true); return; } // value == PlayingState if (d->state == StoppedState) { play(); return; } if (d->state == PausedState) { pause(false); return; } } bool AVPlayer::load() { //qInfo() << __FUNCTION__ << "load start"; if (!d->current_source.isValid()) { qDebug("Invalid media source. No file or IODevice was set."); return false; } if (!d->checkSourceChange() && (mediaStatus() == FAV::LoadingMedia || mediaStatus() == LoadedMedia)) { qInfo() << __FUNCTION__ << "CANCLED"; return true; } if (isLoaded()) { // release codec ctx. if not loaded, they are released by avformat. TODO: always let avformat release them? if (d->adec) { d->adec->setCodecContext(0); } if (d->vdec) { d->vdec->setCodecContext(0); } qInfo() << __FUNCTION__ << "isLoaded"; } d->loaded = false; d->status = LoadingMedia; if (!isAsyncLoad()) { loadInternal(); qInfo() << __FUNCTION__ << "loadInternal"; return d->loaded; } class LoadWorker : public QRunnable { public: LoadWorker(AVPlayer *player) : m_player(player) {} virtual void run() { if (!m_player) return; m_player->loadInternal(); } private: AVPlayer* m_player; }; // TODO: thread pool has a max thread limit loaderThreadPool()->start(new LoadWorker(this)); return true; } void AVPlayer::play() { //qInfo() << "play start" << __FUNCTION__; FLOG_START; // LOG TIMER START //FIXME: bad delay after play from here if (isPlaying()) { qDebug("play() when playing"); if (!d->checkSourceChange()) return; stop(); qInfo() << "play stop" << __FUNCTION__; } // 이미 로딩한 다음 재생시작하는데 다시 로딩프로세스 진입하는 문제 수정 if (!isLoaded() && !load()) { qWarning("load error"); return; } if (isLoaded()) { // !asyncLoad() is here because load() returned true playInternal(); return; } if(!isLoaded()) { connect(this, SIGNAL(loaded()), this, SLOT(playInternal())); } } #if (USE_SKIP_COUNT) void AVPlayer::setSkipCount(int nCount) { g_SkipCount = nCount; // d->setSkipCount(nCount); } #endif #if (SKIP_FIRST_CORRUPT_FRAME) void AVPlayer::setSkipFrameThreadWait(int mSleep) { g_firstCorruptFrameSkipWait = mSleep; } #endif #if (USE_AUDIO_DISABLE) void AVPlayer::setAudioDisable(bool disable) { d->setAudioDisable(disable); } #endif void AVPlayer::playInternal() { #if (LOADING_PERFORMANCE_TEST || FILE_LOADING_TIMELAPSE) gPlayerTimer.start(); #endif //qInfo() << "playInternal start" << __FUNCTION__; // LOCK SCOPE { QMutexLocker lock(&d->load_mutex); Q_UNUSED(lock); if (!d->demuxer.isLoaded()) { return; } d->start_position_norm = normalizedPosition(d->start_position); d->stop_position_norm = normalizedPosition(d->stop_position); // FIXME: if call play() frequently playInternal may not be called if disconnect here disconnect(this, SIGNAL(loaded()), this, SLOT(playInternal())); if (!d->setupAudioThread(this)) { d->read_thread->setAudioThread(0); //set 0 before delete. ptr is used in demux thread when set 0 if (d->athread) { qDebug("release audio thread."); delete d->athread; d->athread = 0;//shared ptr? } } #if (LOADING_PERFORMANCE_TEST || FILE_LOADING_TIMELAPSE) qInfo() << "TIMELAPSE" << gPlayerTimer.elapsed() << __FUNCTION__ << __LINE__; #endif // setupVideoThread 에서 속도가 느려짐 if (!d->setupVideoThread(this)) { d->read_thread->setVideoThread(0); //set 0 before delete. ptr is used in demux thread when set 0 if (d->vthread) { qDebug("release video thread."); delete d->vthread; d->vthread = 0;//shared ptr? } } #if (LOADING_PERFORMANCE_TEST || FILE_LOADING_TIMELAPSE) qInfo() << "TIMELAPSE" << gPlayerTimer.elapsed() << __FUNCTION__ << __LINE__; #endif if (!d->athread && !d->vthread) { d->loaded = false; qWarning("load failed"); return; } // setup clock before avthread.start() becuase avthreads use clock. after avthreads setup because of ao check masterClock()->reset(); // TODO: add isVideo() or hasVideo()? if (masterClock()->isClockAuto()) { qDebug("auto select clock: audio > external"); if (!d->demuxer.audioCodecContext() || !d->ao || !d->ao->isOpen() || !d->athread) { masterClock()->setClockType(AVClock::ExternalClock); qDebug("No audio found or audio not supported. Using ExternalClock."); } else { qDebug("Using AudioClock"); masterClock()->setClockType(AVClock::AudioClock); } } masterClock()->setInitialValue((double)absoluteMediaStartPosition()/1000.0); #if (LOADING_PERFORMANCE_TEST || FILE_LOADING_TIMELAPSE) qInfo() << "TIMELAPSE" << gPlayerTimer.elapsed() << __FUNCTION__ << __LINE__; #endif // 여기까지 문제 발생... #if (FORCE_BREAK_EOF) if(d->vthread != NULL && d->vthread->isRunning()) { d->vthread->forceEnd = true; d->vthread->stop(); while (d->vthread->isRunning()) { QThread::msleep(10); } } if(d->athread != NULL && d->athread->isRunning()) { d->athread->forceEnd = true; d->athread->stop(); while (d->athread->isRunning()) { QThread::msleep(10); } } #endif #if (LOADING_PERFORMANCE_TEST || FILE_LOADING_TIMELAPSE) qInfo() << "TIMELAPSE" << gPlayerTimer.elapsed() << __FUNCTION__ << __LINE__; #endif // from previous play() if (d->demuxer.audioCodecContext() && d->athread) { #if !(OFF_OTHER_DEBUG) qDebug("Starting audio thread..."); #endif d->athread->start(); } if (d->demuxer.videoCodecContext() && d->vthread) { #if !(OFF_OTHER_DEBUG) qDebug("Starting video thread..."); #endif //qInfo() << "### $$$$$ VTHREAD START" << __FUNCTION__; d->vthread->start(); } // 이거 read_thread 가 먼저 시작하면 // frame packet pts 가 4초 등으로 시작하여 문제가 됨 // 반드시 vthread 시작되는지 확인하고 read_thread 시작해야함 if (d->demuxer.audioCodecContext() && d->athread) d->athread->waitForStarted(); if (d->demuxer.videoCodecContext() && d->vthread) d->vthread->waitForStarted(); /// demux thread not started, seek tasks will be cleared d->read_thread->setMediaEndAction(mediaEndAction()); d->read_thread->start(); QThread::msleep(50); d->read_thread->waitForStarted(); if (d->timer_id < 0) { //d->timer_id = startNotifyTimer(); //may fail if not in this thread QMetaObject::invokeMethod(this, "startNotifyTimer", Qt::AutoConnection); } d->state = PlayingState; if (d->repeat_current < 0) d->repeat_current = 0; } //end lock scoped here to avoid dead lock if connect started() to a slot that call unload()/play() if (d->start_position_norm > 0) { if (relativeTimeMode()) setPosition(qint64((d->start_position_norm + absoluteMediaStartPosition()))); else setPosition((qint64)(d->start_position_norm)); } Q_EMIT stateChanged(PlayingState); Q_EMIT started(); //we called stop(), so must emit started() #if (LOADING_PERFORMANCE_TEST || FILE_LOADING_TIMELAPSE) qInfo() << "TIMELAPSE" << gPlayerTimer.elapsed() << __FUNCTION__ << __LINE__; #endif //qInfo() << "playInternal done" << __FUNCTION__; } void AVPlayer::stopFromDemuxerThread() { qDebug("demuxer thread emit finished. repeat: %d/%d", currentRepeat(), repeat()); d->seeking = false; if (currentRepeat() < 0 || (currentRepeat() >= repeat() && repeat() >= 0)) { qreal stop_pts = masterClock()->videoTime(); if (stop_pts <= 0) stop_pts = masterClock()->value(); masterClock()->reset(); QMetaObject::invokeMethod(this, "stopNotifyTimer"); // vars not set by user can be reset d->repeat_current = -1; d->start_position_norm = 0; d->stop_position_norm = kInvalidPosition; // already stopped. so not 0 but invalid. 0 can stop the playback in timerEvent d->media_end = kInvalidPosition; qDebug("avplayer emit stopped()"); d->state = StoppedState; #if (FORCE_BREAK_EOF_LOG2) qInfo() << __FUNCTION__ << __LINE__ << _playerID; #endif QMetaObject::invokeMethod(this, "stateChanged", Q_ARG(FAV::AVPlayer::State, d->state)); QMetaObject::invokeMethod(this, "stopped"); QMetaObject::invokeMethod(this, "stoppedAt", Q_ARG(qint64, qint64(stop_pts*1000.0))); #if (FIX_PLAYER_END_CLIP) QMetaObject::invokeMethod(this, "ended"); #endif // //Q_EMIT stateChanged(d->state); //Q_EMIT stopped(); //Q_EMIT stoppedAt(stop_pts*1000.0); /* * currently preload is not supported. so always unload. Then some properties will be reset, e.g. duration() */ unload(); //TODO: invoke? } else { d->repeat_current++; QMetaObject::invokeMethod(this, "play"); //ensure play() is called from player thread } } void AVPlayer::aboutToQuitApp() { d->reset_state = true; stop(); while (isPlaying()) { qApp->processEvents(); qDebug("about to quit....."); pause(false); // may be paused. then aboutToQuitApp will not finish stop(); } d->demuxer.setInterruptStatus(-1); loaderThreadPool()->waitForDone(); } void AVPlayer::setNotifyInterval(int msec) { if (d->notify_interval == msec) return; if (d->notify_interval < 0 && msec <= 0) return; const int old = qAbs(d->notify_interval); d->notify_interval = msec; d->updateNotifyInterval(); Q_EMIT notifyIntervalChanged(); if (d->timer_id < 0) return; if (old != qAbs(d->notify_interval)) { stopNotifyTimer(); startNotifyTimer(); } } int AVPlayer::notifyInterval() const { return qAbs(d->notify_interval); } void AVPlayer::startNotifyTimer() { d->timer_id = startTimer(qAbs(d->notify_interval)); } void AVPlayer::stopNotifyTimer() { if (d->timer_id < 0) return; killTimer(d->timer_id); d->timer_id = -1; } void AVPlayer::onStarted() { if (d->speed != 1.0) { //TODO: check clock type? if (d->ao && d->ao->isAvailable()) { d->ao->setSpeed(d->speed); } masterClock()->setSpeed(d->speed); } else { d->applyFrameRate(); } } void AVPlayer::updateMediaStatus(FAV::MediaStatus status) { if (status == d->status) return; d->status = status; Q_EMIT mediaStatusChanged(d->status); } #if (PLAY_SYNC_FIX2) void AVPlayer::onSeekFinished(qint64 value,qint64 requested) #else void AVPlayer::onSeekFinished(qint64 value) #endif { d->seeking = false; #if (PLAY_SYNC_FIX2) Q_EMIT seekFinished(requested); #else Q_EMIT seekFinished(value); #endif //d->clock->updateValue(value/1000.0); if (relativeTimeMode()) { Q_EMIT positionChanged(value - absoluteMediaStartPosition()); } else { if(value == 0) { qInfo() << "CATCH"; } // duration over seek 하면 여기서 0 가 발생하는데... //qInfo() << "onSeekFinished normal" << __FUNCTION__; #if (PLAY_SYNC_FIX2) Q_EMIT positionChanged(requested); #else Q_EMIT positionChanged(value); #endif } } void AVPlayer::tryClearVideoRenderers() { if (!d->vthread) { qWarning("internal error"); return; } if (!(mediaEndAction() & MediaEndAction_KeepDisplay)) { d->vthread->clearRenderers(); } } void AVPlayer::stop() { // check d->timer_id, <0 return? if (d->reset_state) { /* * must kill in main thread! If called from user, may be not in main thread. * then timer goes on, and player find that d->stop_position reached(d->stop_position is already * 0 after user call stop), * stop() is called again by player and reset state. but this call is later than demuxer stop. * so if user call play() immediatly, may be stopped by AVPlayer */ // TODO: invokeMethod "stopNotifyTimer" if (d->timer_id >= 0) { qDebug("timer: %d, current thread: %p, player thread: %p", d->timer_id, QThread::currentThread(), thread()); if (QThread::currentThread() == thread()) { //called by user in the same thread as player stopNotifyTimer(); } else { //TODO: post event. } } // vars not set by user can be reset d->start_position_norm = 0; d->stop_position_norm = 0; // 0 can stop play in timerEvent d->media_end = kInvalidPosition; } else { //called by player stopNotifyTimer(); } d->seeking = false; d->reset_state = true; d->repeat_current = -1; if (!isPlaying()) { qDebug("Not playing~"); if (mediaStatus() == LoadingMedia || mediaStatus() == LoadedMedia) { qDebug("loading media: %d", mediaStatus() == LoadingMedia); d->demuxer.setInterruptStatus(-1); } return; } while (d->read_thread->isRunning()) { qDebug("stopping demuxer thread..."); d->read_thread->stop(); d->read_thread->wait(500); // interrupt to quit av_read_frame quickly. d->demuxer.setInterruptStatus(-1); } qDebug("all audio/video threads stopped... state: %d", d->state); } void AVPlayer::timerEvent(QTimerEvent *te) { if (te->timerId() == d->timer_id) { // killTimer() should be in the same thread as object. kill here? if (isPaused()) { //return; //ensure positionChanged emitted for stepForward() // 에서는 발생하지 않도록 한다 } // active only when playing const qint64 t = position(); if (d->stop_position_norm == kInvalidPosition) { // or check d->stop_position_norm < 0 // not seekable. network stream Q_EMIT positionChanged(t); return; } if (t < d->start_position_norm) { //qDebug("position %lld < startPosition %lld", t, d->start_position_norm); // or set clock initial value to get correct t if (d->start_position_norm != mediaStartPosition()) { setPosition(d->start_position_norm); return; } } if (t <= d->stop_position_norm) { if (!d->seeking) { // if(_playerID == 0) { // qInfo() << d->stop_position << d->stop_position_norm << t << "M:" << mediaStopPosition(); // } // 여기서 일반적인 플레이 POSITION 발생함 Q_EMIT positionChanged(t); } return; } // atEnd() supports dynamic changed duration. but we can not break A-B repeat mode, so check stoppos and mediastoppos if ((!d->demuxer.atEnd() || d->read_thread->isRunning()) && stopPosition() >= mediaStopPosition()) { if (!d->seeking) { Q_EMIT positionChanged(t); } return; } // TODO: remove. kill timer in an event; if (d->stop_position_norm == 0) { //stop() by user in other thread, state is already reset d->reset_state = false; qDebug("stopPosition() == 0, stop"); stop(); } // t < d->start_position is ok. d->repeat_max<0 means repeat forever if (currentRepeat() >= repeat() && repeat() >= 0) { d->reset_state = true; // true is default, can remove here qDebug("stopPosition() %lld/%lld reached and no repeat: %d", t, stopPosition(), repeat()); stop(); return; } // FIXME: now stop instead of seek if reach media's end. otherwise will not get eof again if (d->stop_position_norm == mediaStopPosition() || !isSeekable()) { // if not seekable, how it can start to play at specified position? qDebug("normalized stopPosition() == mediaStopPosition() or !seekable. d->repeat_current=%d", d->repeat_current); d->reset_state = false; stop(); // repeat after all threads stopped } else { d->repeat_current++; qDebug("noramlized stopPosition() != mediaStopPosition() and seekable. d->repeat_current=%d", d->repeat_current); setPosition(d->start_position_norm); } } } #if (FIRST_FRAME_NOTIFY) void AVPlayer::onFirstFrameNotify(qreal pts) { Q_EMIT firstFrameNotify(pts); } #endif #if (FIXED_FPS_DURATION) void AVPlayer::onFrameEnded() { Q_EMIT frameEnded(); } #endif #if (FIX_PLAYER_END_CLIP) void AVPlayer::onEnded() { //qInfo() << _playerID << __FUNCTION__ << __LINE__; d->exitThreads(); } #endif // FIX_PLAYER_END_CLIP //FIXME: If not playing, it will just play but not play one frame. /* void AVPlayer::stepForward() { // pause clock pause(true); // must pause AVDemuxThread (set user_paused true) d->read_thread->stepForward(); } void AVPlayer::stepBackward() { d->clock->pause(true); d->state = PausedState; Q_EMIT stateChanged(d->state); Q_EMIT paused(true); d->read_thread->stepBackward(); } */ void AVPlayer::seek(qreal r) { seek(qint64(r*double(duration()))); } void AVPlayer::seek(qint64 pos) { setPosition(pos); } void AVPlayer::seekForward() { seek(position() + kSeekMS); } void AVPlayer::seekBackward() { seek(position() - kSeekMS); } void AVPlayer::setSeekType(SeekType type) { d->seek_type = type; } SeekType AVPlayer::seekType() const { return d->seek_type; } qreal AVPlayer::bufferProgress() const { const PacketBuffer* buf = d->read_thread->buffer(); return buf ? buf->bufferProgress() : 0; } qreal AVPlayer::bufferSpeed() const { const PacketBuffer* buf = d->read_thread->buffer(); return buf ? buf->bufferSpeedInBytes() : 0; } qint64 AVPlayer::buffered() const { const PacketBuffer* buf = d->read_thread->buffer(); return buf ? buf->buffered() : 0LL; } void AVPlayer::setBufferMode(BufferMode mode) { d->buffer_mode = mode; } BufferMode AVPlayer::bufferMode() const { return d->buffer_mode; } void AVPlayer::setBufferValue(qint64 value) { if (d->buffer_value == value) return; d->buffer_value = value; d->updateBufferValue(); } int AVPlayer::bufferValue() const { return d->buffer_value; } void AVPlayer::updateClock(qint64 msecs) { d->clock->updateExternalClock(msecs); } int AVPlayer::brightness() const { return d->brightness; } void AVPlayer::setBrightness(int val) { if (d->brightness == val) return; d->brightness = val; Q_EMIT brightnessChanged(d->brightness); if (d->vthread) { d->vthread->setBrightness(val); } } int AVPlayer::contrast() const { return d->contrast; } void AVPlayer::setContrast(int val) { if (d->contrast == val) return; d->contrast = val; Q_EMIT contrastChanged(d->contrast); if (d->vthread) { d->vthread->setContrast(val); } } int AVPlayer::hue() const { return 0; } void AVPlayer::setHue(int val) { Q_UNUSED(val); } int AVPlayer::saturation() const { return d->saturation; } void AVPlayer::setSaturation(int val) { if (d->saturation == val) return; d->saturation = val; Q_EMIT saturationChanged(d->saturation); if (d->vthread) { d->vthread->setSaturation(val); } } #if (AVFORMAT_FILTER_SPEED) void AVPlayer::setAVFormatFilter(QString& filter) { Q_UNUSED(filter); // AVFormatContext* context = d->demuxer.formatContext(); } #endif #if (RM_TESTING) int AVPlayer::displyedFrameCount() { if(d->vthread == NULL) { return -1; } return d->vthread->displyedFrame; } #endif #if (DEBUG_PLAYER) void AVPlayer::debugInfo() { qInfo() << "---------------------------------------------------------" << "\nsource:" << d->current_source << "\nvideo stream:" << d->video_track << "\nduration:" << duration() << "\nmedia_start_pts:" << d->media_start_pts << "\nmedia_end:" << d->media_end << "\nstart_position:" << d->start_position << "\nstop_position:" << d->stop_position << "\nstart_position_norm:" << d->start_position_norm << "\nstop_position_norm:" << d->stop_position_norm; } #endif } //namespace FAV