first commit

This commit is contained in:
2026-02-21 17:11:31 +09:00
commit 18b4338361
4001 changed files with 365464 additions and 0 deletions

View File

@@ -0,0 +1,824 @@
/******************************************************************************
QtAV: Multimedia framework based on Qt and FFmpeg
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
* This file is part of QtAV (from 2014)
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 "FilterManager.h"
#include "OutputSet.h"
#include "AudioDecoder.h"
#include "AudioFormat.h"
#include "AudioResampler.h"
#include "MediaIO.h"
#include "VideoCapture.h"
#include "AVCompat.h"
#include "Logger.h"
#include <QThread>
namespace FAV {
namespace Internal {
#if (FILE_LOADING_TIMELAPSE)
#include <QElapsedTimer>
QElapsedTimer gPlayerPrivateTimer;
#endif
int computeNotifyPrecision(qint64 duration, qreal fps)
{
#if (FIXED_PLAYER_NOTIFY_INTERVAL)
Q_UNUSED(duration)
Q_UNUSED(fps)
return 200;
#else
if (duration <= 0 || duration > 60*1000) // no duration or 10min
return 500;
if (duration > 20*1000)
return 250;
int dt = 500;
if (fps > 1)
{
dt = qMin(250, int(qreal(dt*2)/fps));
}
else
{
dt = duration / 80; //<= 250
}
return qMax(20, dt);
#endif
}
} // namespace Internal
static bool correct_audio_channels(AVCodecContext *ctx)
{
if (ctx->channels <= 0)
{
if (ctx->channel_layout)
{
ctx->channels = av_get_channel_layout_nb_channels(ctx->channel_layout);
}
}
else
{
if (!ctx->channel_layout)
{
ctx->channel_layout = av_get_default_channel_layout(ctx->channels);
}
}
return ctx->channel_layout > 0 && ctx->channels > 0;
}
AVPlayer::Private::Private()
: auto_load(false)
, async_load(true)
, loaded(false)
, relative_time_mode(true)
, media_start_pts(0)
, media_end(kInvalidPosition)
, reset_state(true)
, start_position(0)
, stop_position(kInvalidPosition)
, start_position_norm(0)
, stop_position_norm(kInvalidPosition)
, repeat_max(0)
, repeat_current(-1)
, timer_id(-1)
, audio_track(0)
, video_track(0)
#if !(DO_NOT_USE_SUBTITLE)
, subtitle_track(0)
#endif
, buffer_mode(BufferPackets)
, buffer_value(-1)
, read_thread(0)
, clock(new AVClock(AVClock::AudioClock))
, vo(0)
, ao(new AudioOutput())
, adec(0)
, vdec(0)
, athread(0)
, vthread(0)
, vcapture(0)
, speed(1.0)
, vos(0)
, aos(0)
, brightness(0)
, contrast(0)
, saturation(0)
, seeking(false)
, seek_type(AccurateSeek)
, interrupt_timeout(30000)
, force_fps(0)
, notify_interval(-500)
, status(NoMedia)
, state(AVPlayer::StoppedState)
, end_action(MediaEndAction_Default)
{
//nSkipCount = 0;
#if (USE_AUDIO_DISABLE)
audio_disabled = false;
#endif
demuxer.setInterruptTimeout(interrupt_timeout);
/*
* reset_state = true;
* must be the same value at the end of stop(), and must be different from value in
* stopFromDemuxerThread()(which is false), so the initial value must be true
*/
vc_ids
#if QTAV_HAVE(DXVA)
//<< VideoDecoderId_DXVA
#endif //QTAV_HAVE(DXVA)
#if QTAV_HAVE(VAAPI)
//<< VideoDecoderId_VAAPI
#endif //QTAV_HAVE(VAAPI)
#if QTAV_HAVE(CEDARV)
<< VideoDecoderId_Cedarv
#endif //QTAV_HAVE(CEDARV)
<< VideoDecoderId_FFmpeg;
}
AVPlayer::Private::~Private() {
// TODO: scoped ptr
if (ao) {
delete ao;
ao = 0;
}
if (adec) {
delete adec;
adec = 0;
}
if (vdec) {
delete vdec;
vdec = 0;
}
if (vos) {
vos->clearOutputs();
delete vos;
vos = 0;
}
if (aos) {
aos->clearOutputs();
delete aos;
aos = 0;
}
if (vcapture) {
delete vcapture;
vcapture = 0;
}
if (clock) {
delete clock;
clock = 0;
}
if (read_thread) {
delete read_thread;
read_thread = 0;
}
}
#if (USE_AUDIO_DISABLE)
void AVPlayer::Private::setAudioDisable(bool disable)
{
audio_disabled = disable;
}
#endif
bool AVPlayer::Private::checkSourceChange()
{
if (current_source.type() == QVariant::String)
{
return demuxer.fileName() != current_source.toString();
}
if (current_source.canConvert<QIODevice*>())
{
return demuxer.ioDevice() != current_source.value<QIODevice*>();
}
return demuxer.mediaIO() != current_source.value<FAV::MediaIO*>();
}
void AVPlayer::Private::updateNotifyInterval()
{
if (notify_interval <= 0) {
notify_interval = -Internal::computeNotifyPrecision(demuxer.duration(), demuxer.frameRate());
}
qDebug("notify_interval: %d", qAbs(notify_interval));
}
void AVPlayer::Private::applyFrameRate()
{
qreal vfps = force_fps;
bool force = vfps > 0;
const bool ao_null = ao && ao->backend().toLower() == QLatin1String("null");
if (athread && !ao_null) { // TODO: no null ao check. null ao block internally
force = vfps > 0 && !!vthread;
} else if (!force) {
force = !!vthread;
vfps = statistics.video.frame_rate > 0 ? statistics.video.frame_rate : 25;
// vfps<0: try to use pts (ExternalClock). if no pts (raw codec), try the default fps(VideoClock)
vfps = -vfps;
}
qreal r = speed;
if (force) {
clock->setClockAuto(false);
// vfps>0: force video fps to vfps. clock must be external
clock->setClockType(vfps > 0 ? AVClock::VideoClock : AVClock::ExternalClock);
vthread->setFrameRate(vfps);
if (statistics.video.frame_rate > 0)
r = qAbs(qreal(vfps))/statistics.video.frame_rate;
} else {
clock->setClockAuto(true);
clock->setClockType(athread && ao->isOpen() ? AVClock::AudioClock : AVClock::ExternalClock);
if (vthread)
vthread->setFrameRate(0.0);
if(ao)
{
ao->setSpeed(1);
}
clock->setSpeed(1);
}
if(ao)
{
ao->setSpeed(r);
}
clock->setSpeed(r);
}
void AVPlayer::Private::initStatistics()
{
initBaseStatistics();
initAudioStatistics(demuxer.audioStream());
initVideoStatistics(demuxer.videoStream());
#if !(DO_NOT_USE_SUBTITLE)
initSubtitleStatistics(demuxer.subtitleStream());
#endif
}
//TODO: av_guess_frame_rate in latest ffmpeg
void AVPlayer::Private::initBaseStatistics()
{
statistics.reset();
statistics.url = current_source.type() == QVariant::String ? current_source.toString() : QString();
statistics.start_time = QTime(0, 0, 0).addMSecs(int(demuxer.startTime()));
statistics.duration = QTime(0, 0, 0).addMSecs((int)demuxer.duration());
AVFormatContext *fmt_ctx = demuxer.formatContext();
if (!fmt_ctx) {
qWarning("demuxer.formatContext()==null. internal error");
updateNotifyInterval();
return;
}
statistics.bit_rate = fmt_ctx->bit_rate;
statistics.format = QString().sprintf("%s - %s", fmt_ctx->iformat->name, fmt_ctx->iformat->long_name);
//AV_TIME_BASE_Q: msvc error C2143
//fmt_ctx->duration may be AV_NOPTS_VALUE. AVDemuxer.duration deals with this case
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(fmt_ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
statistics.metadata.insert(QString::fromUtf8(tag->key), QString::fromUtf8(tag->value));
}
updateNotifyInterval();
}
void AVPlayer::Private::initCommonStatistics(int s, Statistics::Common *st, AVCodecContext *avctx)
{
AVFormatContext *fmt_ctx = demuxer.formatContext();
if (!fmt_ctx) {
qWarning("demuxer.formatContext()==null. internal error");
return;
}
AVStream *stream = fmt_ctx->streams[s];
qDebug("stream: %d, duration=%lld (%lld ms), time_base=%f", s, stream->duration, qint64(qreal(stream->duration)*av_q2d(stream->time_base)*1000.0), av_q2d(stream->time_base));
// AVCodecContext.codec_name is deprecated. use avcodec_get_name. check null avctx->codec?
st->codec = QLatin1String(avcodec_get_name(avctx->codec_id));
st->codec_long = QLatin1String(get_codec_long_name(avctx->codec_id));
st->total_time = QTime(0, 0, 0).addMSecs(stream->duration == (qint64)AV_NOPTS_VALUE ? 0 : int(qreal(stream->duration)*av_q2d(stream->time_base)*1000.0));
st->start_time = QTime(0, 0, 0).addMSecs(stream->start_time == (qint64)AV_NOPTS_VALUE ? 0 : int(qreal(stream->start_time)*av_q2d(stream->time_base)*1000.0));
qDebug("codec: %s(%s)", qPrintable(st->codec), qPrintable(st->codec_long));
st->bit_rate = avctx->bit_rate; //fmt_ctx
st->frames = stream->nb_frames;
if (stream->avg_frame_rate.den && stream->avg_frame_rate.num)
st->frame_rate = av_q2d(stream->avg_frame_rate);
#if (defined FF_API_R_FRAME_RATE && FF_API_R_FRAME_RATE) //removed in libav10
//FIXME: which 1 should we choose? avg_frame_rate may be nan, r_frame_rate may be wrong(guessed value)
else if (stream->r_frame_rate.den && stream->r_frame_rate.num) {
st->frame_rate = av_q2d(stream->r_frame_rate);
qDebug("%d/%d", stream->r_frame_rate.num, stream->r_frame_rate.den);
}
#endif //FF_API_R_FRAME_RATE
//http://ffmpeg.org/faq.html#AVStream_002er_005fframe_005frate-is-wrong_002c-it-is-much-larger-than-the-frame-rate_002e
//http://libav-users.943685.n4.nabble.com/Libav-user-Reading-correct-frame-rate-fps-of-input-video-td4657666.html
//qDebug("time: %f~%f, nb_frames=%lld", st->start_time, st->total_time, stream->nb_frames); //why crash on mac? av_q2d({0,0})?
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
st->metadata.insert(QString::fromUtf8(tag->key), QString::fromUtf8(tag->value));
}
}
void AVPlayer::Private::initAudioStatistics(int s)
{
AVCodecContext *avctx = demuxer.audioCodecContext();
statistics.audio = Statistics::Common();
statistics.audio_only = Statistics::AudioOnly();
if (!avctx)
return;
statistics.audio.available = s == demuxer.audioStream();
initCommonStatistics(s, &statistics.audio, avctx);
if (adec) {
statistics.audio.decoder = adec->name();
statistics.audio.decoder_detail = adec->description();
}
correct_audio_channels(avctx);
statistics.audio_only.block_align = avctx->block_align;
statistics.audio_only.channels = avctx->channels;
char cl[128]; //
// nb_channels -1: will use av_get_channel_layout_nb_channels
av_get_channel_layout_string(cl, sizeof(cl), avctx->channels, avctx->channel_layout);
statistics.audio_only.channel_layout = QLatin1String(cl);
statistics.audio_only.sample_fmt = QLatin1String(av_get_sample_fmt_name(avctx->sample_fmt));
statistics.audio_only.frame_size = avctx->frame_size;
statistics.audio_only.sample_rate = avctx->sample_rate;
}
void AVPlayer::Private::initVideoStatistics(int s)
{
AVCodecContext *avctx = demuxer.videoCodecContext();
statistics.video = Statistics::Common();
statistics.video_only = Statistics::VideoOnly();
if (!avctx)
{
return;
}
statistics.video.available = s == demuxer.videoStream();
initCommonStatistics(s, &statistics.video, avctx);
if (vdec)
{
statistics.video.decoder = vdec->name();
statistics.video.decoder_detail = vdec->description();
}
statistics.video_only.coded_height = avctx->coded_height;
statistics.video_only.coded_width = avctx->coded_width;
statistics.video_only.gop_size = avctx->gop_size;
statistics.video_only.pix_fmt = QLatin1String(av_get_pix_fmt_name(avctx->pix_fmt));
statistics.video_only.height = avctx->height;
statistics.video_only.width = avctx->width;
}
// notify statistics change after audio/video thread is set
bool AVPlayer::Private::setupAudioThread(AVPlayer *player)
{
#if (DISABLE_AUDIO_THREAD)
return false;
#endif
#if (USE_AUDIO_DISABLE)
if(audio_disabled == true)
{
return true;
}
#endif
#if (FILE_LOADING_TIMELAPSE)
Internal::gPlayerPrivateTimer.start();
#endif
AVDemuxer *ademuxer = &demuxer;
if (!external_audio.isEmpty())
{
ademuxer = &audio_demuxer;
}
ademuxer->setStreamIndex(AVDemuxer::AudioStream, audio_track);
// pause demuxer, clear queues, set demuxer stream, set decoder, set ao, resume
// clear packets before stream changed
if (athread)
{
athread->packetQueue()->clear();
athread->setDecoder(0);
athread->setOutput(0);
}
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
AVCodecContext *avctx = ademuxer->audioCodecContext();
if (!avctx)
{
// TODO: close ao? //TODO: check pulseaudio perapp control if closed
return false;
}
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
qDebug("has audio");
// TODO: no delete, just reset avctx and reopen
if (adec) {
adec->disconnect();
delete adec;
adec = 0;
}
adec = AudioDecoder::create();
if (!adec)
{
qWarning("failed to create audio decoder");
return false;
}
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
QObject::connect(adec, SIGNAL(error(FAV::AVError)), player, SIGNAL(error(FAV::AVError)));
adec->setCodecContext(avctx);
adec->setOptions(ac_opt);
if (!adec->open()) {
AVError e(AVError::AudioCodecNotFound);
qWarning() << e.string();
emit player->error(e);
return false;
}
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
correct_audio_channels(avctx);
AudioFormat af;
af.setSampleRate(avctx->sample_rate);
af.setSampleFormatFFmpeg(avctx->sample_fmt);
af.setChannelLayoutFFmpeg(avctx->channel_layout);
if (!af.isValid()) {
qWarning("invalid audio format. audio stream will be disabled");
return false;
}
//af.setChannels(avctx->channels);
// always reopen to ensure internal buffer queue inside audio backend(openal) is clear. also make it possible to change backend when replay.
//if (ao->audioFormat() != af) {
//qDebug("ao audio format is changed. reopen ao");
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
ao->setAudioFormat(af); /// set before close to workaround OpenAL context lost
ao->close();
#if !(OFF_OTHER_DEBUG)
qDebug() << "AudioOutput format: " << ao->audioFormat() << "; requested: " << ao->requestedFormat();
#endif
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
// PORT AUDIO 1초 걸림...
if (!ao->open()) {
return false;
}
//}
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
adec->resampler()->setOutAudioFormat(ao->audioFormat());
// no need to set resampler if AudioFrame is used
#if !USE_AUDIO_FRAME
adec->resampler()->inAudioFormat().setSampleFormatFFmpeg(avctx->sample_fmt);
adec->resampler()->inAudioFormat().setSampleRate(avctx->sample_rate);
adec->resampler()->inAudioFormat().setChannels(avctx->channels);
adec->resampler()->inAudioFormat().setChannelLayoutFFmpeg(avctx->channel_layout);
#endif
if (audio_track < 0)
{
return true;
}
#if (FILE_LOADING_TIMELAPSE)
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
if (!athread)
{
qDebug("new audio thread");
athread = new AudioThread(player);
athread->playerID = player->playerID;
athread->setClock(clock);
athread->setStatistics(&statistics);
athread->setOutputSet(aos);
qDebug("demux thread setAudioThread");
read_thread->setAudioThread(athread);
//reconnect if disconnected
QList<Filter*> filters = FilterManager::instance().audioFilters(player);
//TODO: isEmpty()==false but size() == 0 in debug mode, it's a Qt bug? we can not just foreach without check empty in debug mode
if (filters.size() > 0) {
foreach (Filter *filter, filters) {
athread->installFilter(filter);
}
}
}
#if (FIXED_FPS_DURATION)
athread->stopPosition = demuxer.durationFixed;
#if (PREVENT_OVER_DURATION_RENDER)
athread->duration = ((qreal)demuxer.originalDuraiont) / 1000000.0;
//qInfo() << athread->playerID << athread->duration << demuxer.originalDuraiont << __FUNCTION__;
#endif // PREVENT_OVER_DURATION_RENDER
#endif // PREVENT_OVER_DURATION_RENDER
athread->setDecoder(adec);
setAVOutput(ao, ao, athread);
updateBufferValue(athread->packetQueue());
initAudioStatistics(ademuxer->audioStream());
#if (FILE_LOADING_TIMELAPSE)
qInfo() << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
#endif
return true;
}
QVariantList AVPlayer::Private::getTracksInfo(AVDemuxer *demuxer, AVDemuxer::StreamType st)
{
QVariantList info;
if (!demuxer)
return info;
QList<int> streams;
switch (st) {
case AVDemuxer::AudioStream:
streams = demuxer->audioStreams();
break;
#if !(DO_NOT_USE_SUBTITLE)
case AVDemuxer::SubtitleStream:
streams = demuxer->subtitleStreams();
break;
#endif
case AVDemuxer::VideoStream:
streams = demuxer->videoStreams();
default:
break;
}
if (streams.isEmpty())
return info;
foreach (int s, streams) {
QVariantMap t;
t[QStringLiteral("id")] = info.size();
t[QStringLiteral("file")] = demuxer->fileName();
AVStream *stream = demuxer->formatContext()->streams[s];
AVCodecContext *ctx = stream->codec;
if (ctx) {
t[QStringLiteral("codec")] = QByteArray(avcodec_descriptor_get(ctx->codec_id)->name);
if (ctx->extradata)
t[QStringLiteral("extra")] = QByteArray((const char*)ctx->extradata, ctx->extradata_size);
}
AVDictionaryEntry *tag = av_dict_get(stream->metadata, "language", NULL, 0);
if (!tag)
tag = av_dict_get(stream->metadata, "lang", NULL, 0);
if (tag) {
t[QStringLiteral("language")] = QString::fromUtf8(tag->value);
}
tag = av_dict_get(stream->metadata, "title", NULL, 0);
if (tag) {
t[QStringLiteral("title")] = QString::fromUtf8(tag->value);
}
info.push_back(t);
}
//QVariantMap t;
//t[QStringLiteral("id")] = -1;
//info.prepend(t);
return info;
}
#if !(DO_NOT_USE_SUBTITLE)
bool AVPlayer::Private::applySubtitleStream(int n, AVPlayer *player)
{
if (!demuxer.setStreamIndex(AVDemuxer::SubtitleStream, n))
return false;
AVCodecContext *ctx = demuxer.subtitleCodecContext();
if (!ctx)
return false;
// FIXME: AVCodecDescriptor.name and AVCodec.name are different!
const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ctx->codec_id);
QByteArray codec(codec_desc->name);
if (ctx->extradata)
Q_EMIT player->internalSubtitleHeaderRead(codec, QByteArray((const char*)ctx->extradata, ctx->extradata_size));
else
Q_EMIT player->internalSubtitleHeaderRead(codec, QByteArray());
return true;
}
#endif
bool AVPlayer::Private::tryApplyDecoderPriority(AVPlayer *player)
{
// TODO: add an option to apply the new decoder even if not available
qint64 pos = player->position();
VideoDecoder *vd = NULL;
AVCodecContext *avctx = demuxer.videoCodecContext();
foreach(VideoDecoderId vid, vc_ids)
{
qDebug("**********trying video decoder: %s...", VideoDecoder::name(vid));
vd = VideoDecoder::create(vid);
if (!vd)
continue;
vd->setCodecContext(avctx); // It's fine because AVDecoder copy the avctx properties
vd->setOptions(vc_opt);
if (vd->open()) {
qDebug("**************Video decoder found:%p", vd);
break;
}
delete vd;
vd = 0;
}
qDebug("**************set new decoder:%p -> %p", vdec, vd);
if (!vd)
{
Q_EMIT player->error(AVError(AVError::VideoCodecNotFound));
return false;
}
if (vd->id() == vdec->id()
&& vd->options() == vdec->options())
{
qDebug("Video decoder does not change");
delete vd;
return true;
}
vthread->packetQueue()->clear();
vthread->setDecoder(vd);
// MUST delete decoder after video thread set the decoder to ensure the deleted vdec will not be used in vthread!
if (vdec)
{
delete vdec;
}
vdec = vd;
QObject::connect(vdec, SIGNAL(error(FAV::AVError)), player, SIGNAL(error(FAV::AVError)));
initVideoStatistics(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);
return true;
}
#if (FIX_PLAYER_END_CLIP)
void AVPlayer::Private::exitThreads()
{
if(vthread) {
vthread->endStopBy();
}
if(athread) {
athread->endStopBy();
}
if(read_thread) {
read_thread->stop();
}
}
#endif
bool AVPlayer::Private::setupVideoThread(AVPlayer *player)
{
demuxer.setStreamIndex(AVDemuxer::VideoStream, video_track);
// pause demuxer, clear queues, set demuxer stream, set decoder, set ao, resume
// clear packets before stream changed
if (vthread)
{
vthread->packetQueue()->clear();
vthread->setDecoder(0);
#if (WAIT_CHANNEL_CHANGE)
QThread::msleep(500);
#endif
//qInfo() << "3.1.1 decoder deleted";
}
AVCodecContext *avctx = demuxer.videoCodecContext();
if (!avctx) {
return false;
}
if (vdec) {
vdec->disconnect();
delete vdec;
vdec = 0;
}
foreach(VideoDecoderId vid, vc_ids) {
qDebug("**********trying video decoder: %s...", VideoDecoder::name(vid));
VideoDecoder *vd = VideoDecoder::create(vid);
if (!vd) {
continue;
}
//vd->isAvailable() //TODO: the value is wrong now
vd->setCodecContext(avctx);
vd->setOptions(vc_opt);
if (vd->open()) {
vdec = vd;
// qInfo() << "3.1.2 decoder created";
qDebug("**************Video decoder found:%p", vdec);
break;
}
delete vd;
}
if (!vdec) {
// DO NOT emit error signals in VideoDecoder::open(). 1 signal is enough
AVError e(AVError::VideoCodecNotFound);
qWarning() << e.string();
emit player->error(e);
return false;
}
QObject::connect(vdec, SIGNAL(error(FAV::AVError)), player, SIGNAL(error(FAV::AVError)));
if (!vthread)
{
vthread = new VideoThread(player);
vthread->playerID = player->playerID;
#if (FIXED_FPS_DURATION)
vthread->stopPosition = demuxer.durationFixed;
//vthread->setPriority(QThread::TimeCriticalPriority);
#endif
vthread->setClock(clock);
vthread->setStatistics(&statistics);
vthread->setVideoCapture(vcapture);
vthread->setOutputSet(vos);
read_thread->setVideoThread(vthread);
#if (FIRST_FRAME_NOTIFY)
QObject::connect(vthread,SIGNAL(firstFrameNotify(qreal)),player,SLOT(onFirstFrameNotify(qreal)));
#endif
#if (FIXED_FPS_DURATION)
QObject::connect(vthread,SIGNAL(frameEnded()),player,SLOT(onFrameEnded()),Qt::DirectConnection);
#endif
QList<Filter*> filters = FilterManager::instance().videoFilters(player);
if (filters.size() > 0) {
foreach (Filter *filter, filters) {
vthread->installFilter(filter);
}
}
QObject::connect(vthread, SIGNAL(finished()), player, SLOT(tryClearVideoRenderers()), Qt::DirectConnection);
}
vthread->setDecoder(vdec);
// 항상실행
#if (FIXED_FPS_DURATION)
vthread->stopPosition = demuxer.durationFixed;
#if (PREVENT_OVER_DURATION_RENDER)
vthread->duration = ((qreal)demuxer.originalDuraiont) / 1000000.0;
#endif // PREVENT_OVER_DURATION_RENDER
#endif // PREVENT_OVER_DURATION_RENDER
#if (PLAY_SYNC_FIX2)
// SEEK 문제없도록 LASTSEEK PTS 추가
//qInfo() << "EOF_PACKET_PTS" << demuxer.durationFixed << (demuxer.frameDuration * 1000) << __FUNCTION__;
vthread->EOFPacketPTS = ((double)(demuxer.durationFixed - (demuxer.frameDuration * 1000)) / 1000000);
// if(demuxer.frameCount > 100) {
// vthread->EOFPacketPTS -= 1;
// }
#endif //
vthread->setBrightness(brightness);
vthread->setContrast(contrast);
vthread->setSaturation(saturation);
updateBufferValue(vthread->packetQueue());
initVideoStatistics(demuxer.videoStream());
// qInfo() << "3.2 setupVideoThread done";
return true;
}
// TODO: set to a lower value when buffering
void AVPlayer::Private::updateBufferValue(PacketBuffer* buf)
{
const bool video = vthread && buf == vthread->packetQueue();
const qreal fps = qMax<qreal>(24.0, statistics.video.frame_rate);
qint64 bv = 0.5*fps;
if (!video)
{
// if has video, then audio buffer should not block the video buffer (bufferValue == 1, modified in AVDemuxThread)
bv = statistics.audio.frame_rate > 0 && statistics.audio.frame_rate < 60 ?
statistics.audio.frame_rate : 3LL;
}
if (buffer_mode == BufferTime)
{
bv = 600LL; //ms
}
else if (buffer_mode == BufferBytes)
{
bv = 1024LL;
}
// no block for music with cover
if (video)
{
if (demuxer.hasAttacedPicture() || (statistics.video.frames > 0 && statistics.video.frames < bv))
{
bv = qMax<qint64>(1LL, statistics.video.frames);
}
}
buf->setBufferMode(buffer_mode);
buf->setBufferValue(buffer_value < 0LL ? bv : buffer_value);
}
void AVPlayer::Private::updateBufferValue()
{
if (athread)
{
updateBufferValue(athread->packetQueue());
}
if (vthread)
{
updateBufferValue(vthread->packetQueue());
}
}
} //namespace FAV