2056 lines
57 KiB
C++
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
|