Files
fmviewer3/project/fm_viewer/fav/AVPlayer.cpp
2026-02-21 17:11:31 +09:00

2056 lines
57 KiB
C++

/******************************************************************************
QtAV: Multimedia framework based on Qt and FFmpeg
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
* This file is part of QtAV
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
******************************************************************************/
#include "AVPlayerPrivate.h"
#include <limits>
#include <QtCore/QCoreApplication>
#include <QtCore/QEvent>
#include <QtCore/QDir>
#include <QtCore/QIODevice>
#include <QtCore/QThreadPool>
#include <QtCore/QTimer>
#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>
QElapsedTimer gPlayerTimer;
#endif
#define EOF_ISSUE_SOLVED 0
namespace FAV {
namespace {
static const struct RegisterMetaTypes {
inline RegisterMetaTypes() {
qRegisterMetaType<FAV::AVPlayer::State>(); // 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<Filter*> filters(FilterManager::instance().videoFilters(this));
foreach (Filter* f, filters) {
uninstallFilter(reinterpret_cast<VideoFilter*>(f));
}
filters = FilterManager::instance().audioFilters(this);
foreach (Filter* f, filters) {
uninstallFilter(reinterpret_cast<AudioFilter*>(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<VideoRenderer*>(d->vos->outputs().last());
}
QList<VideoRenderer*> AVPlayer::videoOutputs()
{
if (!d->vos)
return QList<VideoRenderer*>();
QList<VideoRenderer*> vos;
vos.reserve(d->vos->outputs().size());
foreach (AVOutput *out, d->vos->outputs()) {
vos.append(static_cast<VideoRenderer*>(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<Filter*> AVPlayer::audioFilters() const
{
return FilterManager::instance().audioFilters((AVPlayer*)this);
}
QList<Filter*> AVPlayer::videoFilters() const
{
return FilterManager::instance().videoFilters((AVPlayer*)this);
}
void AVPlayer::setPriority(const QVector<VideoDecoderId> &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<typename ID, typename T>
static QVector<ID> idsFromNames(const QStringList& names) {
QVector<ID> 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<VideoDecoderId, VideoDecoder>(names));
}
template<typename ID, typename T>
static QStringList idsToNames(QVector<ID> 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<VideoDecoderId, VideoDecoder>(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<QIODevice*>()) {
d->reset_state = d->current_source.value<QIODevice*>() != 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<QIODevice*>()) {
d->reset_state = true;
} else { // MediaIO
d->reset_state = d->current_source.value<FAV::MediaIO*>() != in;
}
}
d->loaded = false;
d->current_source = QVariant::fromValue<FAV::MediaIO*>(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<FAV::MediaIO*>())
return 0;
return d->current_source.value<FAV::MediaIO*>();
}
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<QIODevice*>())
{
d->demuxer.setMedia(d->current_source.value<QIODevice*>());
}
else
{ // MediaIO
d->demuxer.setMedia(d->current_source.value<FAV::MediaIO*>());
}
}
// 로딩에서 시간 가장 많이 걸리는 부분
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<int>::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