/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * 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 namespace FAV { namespace Internal { #if (FILE_LOADING_TIMELAPSE) #include 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()) { return demuxer.ioDevice() != current_source.value(); } return demuxer.mediaIO() != current_source.value(); } 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 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 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 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(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(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