first commit
This commit is contained in:
824
project/fm_viewer/fav/AVPlayerPrivate.cpp
Normal file
824
project/fm_viewer/fav/AVPlayerPrivate.cpp
Normal 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
|
||||
Reference in New Issue
Block a user