/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * This file is part of QtAV (from 2013) 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 "VideoDecoderFFmpegBase.h" #include "AVCompat.h" #include "factory.h" #include "version.h" #include "Logger.h" #define REMOVE_FFMPEG_HW 1 /*! * options (properties) are from libavcodec/options_table.h * enum name here must convert to lower case to fit the names in avcodec. done in AVDecoder.setOptions() * Don't use lower case here because the value name may be "default" in avcodec which is a keyword of C++ */ namespace FAV { class VideoDecoderFFmpegPrivate; class VideoDecoderFFmpeg : public VideoDecoderFFmpegBase { Q_OBJECT DPTR_DECLARE_PRIVATE(VideoDecoderFFmpeg) Q_PROPERTY(QString codecName READ codecName WRITE setCodecName NOTIFY codecNameChanged) #if !(REMOVE_FFMPEG_HW) Q_PROPERTY(QString hwaccel READ hwaccel WRITE setHwaccel NOTIFY hwaccelChanged) #endif Q_PROPERTY(DiscardType skip_loop_filter READ skipLoopFilter WRITE setSkipLoopFilter) Q_PROPERTY(DiscardType skip_idct READ skipIDCT WRITE setSkipIDCT) // Force a strict standard compliance when encoding (accepted values: -2 to 2) //Q_PROPERTY(StrictType strict READ strict WRITE setStrict) Q_PROPERTY(DiscardType skip_frame READ skipFrame WRITE setSkipFrame) Q_PROPERTY(int threads READ threads WRITE setThreads) // 0 is auto Q_PROPERTY(ThreadFlags thread_type READ threadFlags WRITE setThreadFlags) Q_PROPERTY(MotionVectorVisFlags vismv READ motionVectorVisFlags WRITE setMotionVectorVisFlags) //Q_PROPERTY(BugFlags bug READ bugFlags WRITE setBugFlags) Q_ENUMS(StrictType) Q_ENUMS(DiscardType) Q_ENUMS(ThreadFlag) Q_FLAGS(ThreadFlags) Q_ENUMS(MotionVectorVisFlag) Q_FLAGS(MotionVectorVisFlags) Q_ENUMS(BugFlag) Q_FLAGS(BugFlags) public: enum StrictType { Very = FF_COMPLIANCE_VERY_STRICT, Strict = FF_COMPLIANCE_STRICT, Normal = FF_COMPLIANCE_NORMAL, //default Unofficial = FF_COMPLIANCE_UNOFFICIAL, Experimental = FF_COMPLIANCE_EXPERIMENTAL }; enum DiscardType { // TODO: discard_type None = AVDISCARD_NONE, Default = AVDISCARD_DEFAULT, //default NoRef = AVDISCARD_NONREF, Bidir = AVDISCARD_BIDIR, NoKey = AVDISCARD_NONKEY, All = AVDISCARD_ALL }; enum ThreadFlag { DefaultType = FF_THREAD_SLICE | FF_THREAD_FRAME,//default Slice = FF_THREAD_SLICE, Frame = FF_THREAD_FRAME }; Q_DECLARE_FLAGS(ThreadFlags, ThreadFlag) // flags. visualize motion vectors (MVs) enum MotionVectorVisFlag { No = 0, //default PF = FF_DEBUG_VIS_MV_P_FOR, BF = FF_DEBUG_VIS_MV_B_FOR, BB = FF_DEBUG_VIS_MV_B_BACK }; Q_DECLARE_FLAGS(MotionVectorVisFlags, MotionVectorVisFlag) enum BugFlag { autodetect = FF_BUG_AUTODETECT, //default #if FF_API_OLD_MSMPEG4 //old_msmpeg4 = FF_BUG_OLD_MSMPEG4, //moc does not support PP? #endif xvid_ilace = FF_BUG_XVID_ILACE, ump4 = FF_BUG_UMP4, no_padding = FF_BUG_NO_PADDING, amv = FF_BUG_AMV, #if FF_API_AC_VLC //ac_vlc = FF_BUG_AC_VLC, //moc does not support PP? #endif qpel_chroma = FF_BUG_QPEL_CHROMA, std_qpel = FF_BUG_QPEL_CHROMA2, direct_blocksize = FF_BUG_DIRECT_BLOCKSIZE, edge = FF_BUG_EDGE, hpel_chroma = FF_BUG_HPEL_CHROMA, dc_clip = FF_BUG_DC_CLIP, ms = FF_BUG_MS, trunc = FF_BUG_TRUNCATED }; Q_DECLARE_FLAGS(BugFlags, BugFlag) static VideoDecoder* createMMAL() { VideoDecoderFFmpeg *vd = new VideoDecoderFFmpeg(); vd->setProperty("hwaccel", "mmal"); return vd; } static VideoDecoder* createQSV() { VideoDecoderFFmpeg *vd = new VideoDecoderFFmpeg(); vd->setProperty("hwaccel", "qsv"); return vd; } static VideoDecoder* createCrystalHD() { VideoDecoderFFmpeg *vd = new VideoDecoderFFmpeg(); vd->setProperty("hwaccel", "crystalhd"); return vd; } #if !(REMOVE_FFMPEG_HW) static void registerHWA() { #if defined(Q_OS_WIN32) || (defined(Q_OS_LINUX) && !defined(Q_PROCESSOR_ARM) && !defined(QT_ARCH_ARM)) VideoDecoder::Register(VideoDecoderId_QSV, createQSV, "QSV"); #endif #ifdef Q_OS_LINUX #if defined(Q_PROCESSOR_ARM)/*qt5*/ || defined(QT_ARCH_ARM) /*qt4*/ VideoDecoder::Register(VideoDecoderId_MMAL, createMMAL, "MMAL"); #else VideoDecoder::Register(VideoDecoderId_CrystalHD, createCrystalHD, "CrystalHD"); #endif #endif } #endif // REMOVE_FFMPEG_HW VideoDecoderFFmpeg(); VideoDecoderId id() const Q_DECL_OVERRIDE Q_DECL_FINAL; QString description() const Q_DECL_OVERRIDE Q_DECL_FINAL { const int patch = QTAV_VERSION_PATCH(avcodec_version()); return QStringLiteral("%1 avcodec %2.%3.%4") .arg(patch>=100?QStringLiteral("FFmpeg"):QStringLiteral("Libav")) .arg(QTAV_VERSION_MAJOR(avcodec_version())).arg(QTAV_VERSION_MINOR(avcodec_version())).arg(patch); } virtual VideoFrame frame() Q_DECL_OVERRIDE Q_DECL_FINAL; // TODO: av_opt_set in setter void setSkipLoopFilter(DiscardType value); DiscardType skipLoopFilter() const; void setSkipIDCT(DiscardType value); DiscardType skipIDCT() const; void setStrict(StrictType value); StrictType strict() const; void setSkipFrame(DiscardType value); DiscardType skipFrame() const; void setThreads(int value); int threads() const; void setThreadFlags(ThreadFlags value); ThreadFlags threadFlags() const; void setMotionVectorVisFlags(MotionVectorVisFlags value); MotionVectorVisFlags motionVectorVisFlags() const; void setBugFlags(BugFlags value); BugFlags bugFlags() const; #if !(REMOVE_FFMPEG_HW) void setHwaccel(const QString& value); QString hwaccel() const; #endif Q_SIGNALS: void codecNameChanged() Q_DECL_OVERRIDE; #if !(REMOVE_FFMPEG_HW) void hwaccelChanged(); #endif }; extern VideoDecoderId VideoDecoderId_FFmpeg; FACTORY_REGISTER(VideoDecoder, FFmpeg, "FFmpeg") #if !(REMOVE_FFMPEG_HW) void RegisterFFmpegHWA_Man() { VideoDecoderFFmpeg::registerHWA(); } namespace { static const struct factory_register_FFmpegHWA { inline factory_register_FFmpegHWA() { VideoDecoderFFmpeg::registerHWA(); } } sInit_FFmpegHWA; } #endif // #if !(REMOVE_FFMPEG_HW) class VideoDecoderFFmpegPrivate Q_DECL_FINAL: public VideoDecoderFFmpegBasePrivate { public: VideoDecoderFFmpegPrivate(): VideoDecoderFFmpegBasePrivate() , skip_loop_filter(VideoDecoderFFmpeg::Default) , skip_idct(VideoDecoderFFmpeg::Default) , strict(VideoDecoderFFmpeg::Normal) , skip_frame(VideoDecoderFFmpeg::Default) , thread_type(VideoDecoderFFmpeg::DefaultType) // , threads(0) , debug_mv(VideoDecoderFFmpeg::No) , bug(VideoDecoderFFmpeg::autodetect) { } bool open() Q_DECL_OVERRIDE { av_opt_set_int(codec_ctx, "skip_loop_filter", (int64_t)skip_loop_filter, 0); av_opt_set_int(codec_ctx, "skip_idct", (int64_t)skip_idct, 0); av_opt_set_int(codec_ctx, "strict", (int64_t)strict, 0); av_opt_set_int(codec_ctx, "skip_frame", (int64_t)skip_frame, 0); av_opt_set_int(codec_ctx, "threads", (int64_t)threads, 0); av_opt_set_int(codec_ctx, "thread_type", (int64_t)thread_type, 0); av_opt_set_int(codec_ctx, "vismv", (int64_t)debug_mv, 0); av_opt_set_int(codec_ctx, "bug", (int64_t)bug, 0); //qInfo() << "thread_type:" << thread_type; //CODEC_FLAG_EMU_EDGE: deprecated in ffmpeg >=? & libav>=10. always set by ffmpeg #if 0 if (fast) { codec_ctx->flags2 |= CODEC_FLAG2_FAST; // TODO: } else { //codec_ctx->flags2 &= ~CODEC_FLAG2_FAST; //ffplay has no this } // lavfilter //codec_ctx->slice_flags |= SLICE_FLAG_ALLOW_FIELD; //lavfilter //codec_ctx->strict_std_compliance = FF_COMPLIANCE_STRICT; codec_ctx->thread_safe_callbacks = true; switch (codec_ctx->codec_id) { case QTAV_CODEC_ID(MPEG4): case QTAV_CODEC_ID(H263): codec_ctx->thread_type = 0; break; case QTAV_CODEC_ID(MPEG1VIDEO): case QTAV_CODEC_ID(MPEG2VIDEO): codec_ctx->thread_type &= ~FF_THREAD_SLICE; /* fall through */ # if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 1, 0)) case QTAV_CODEC_ID(H264): case QTAV_CODEC_ID(VC1): case QTAV_CODEC_ID(WMV3): codec_ctx->thread_type &= ~FF_THREAD_FRAME; # endif default: break; } #endif return true; } int skip_loop_filter; int skip_idct; int strict; int skip_frame; int thread_type; int threads; int debug_mv; int bug; QString hwa; }; VideoDecoderFFmpeg::VideoDecoderFFmpeg(): VideoDecoderFFmpegBase(*new VideoDecoderFFmpegPrivate()) { // dynamic properties about static property details. used by UI // format: detail_property setProperty("detail_skip_loop_filter", "Skipping the loop filter (aka deblocking) usually has determinal effect on quality. However it provides a big speedup for hi definition streams"); // like skip_frame setProperty("detail_skip_idct", "Force skipping of idct to speed up decoding for frame types (-1=None, " "0=Default, 1=B-frames, 2=P-frames, 3=B+P frames, 4=all frames)"); setProperty("detail_skip_frame","Force skipping frames for speed up decoding."); setProperty("detail_threads", QString("%1\n%2\n%3") .arg("Number of decoding threads. Set before open. Maybe no effect for some decoders") .arg("0: auto") .arg("1: single thread decoding")); } VideoDecoderId VideoDecoderFFmpeg::id() const { #if !(REMOVE_FFMPEG_HW) DPTR_D(const VideoDecoderFFmpeg); if (d.hwa == QLatin1String("mmal")) return VideoDecoderId_MMAL; if (d.hwa == QLatin1String("qsv")) return VideoDecoderId_QSV; if (d.hwa == QLatin1String("crystalhd")) return VideoDecoderId_CrystalHD; #endif return VideoDecoderId_FFmpeg; } #if (1) VideoFrame VideoDecoderFFmpeg::frame() { DPTR_D(VideoDecoderFFmpeg); if (d.frame->width <= 0 || d.frame->height <= 0 || !d.codec_ctx) return VideoFrame(); // it's safe if width, height, pixfmt will not change, only data change VideoFrame frame(d.frame->width, d.frame->height, VideoFormat((int)d.codec_ctx->pix_fmt)); frame.setDisplayAspectRatio(d.getDAR(d.frame)); frame.setBits(d.frame->data); frame.setBytesPerLine(d.frame->linesize); // in s. TODO: what about AVFrame.pts? av_frame_get_best_effort_timestamp? move to VideoFrame::from(AVFrame*) //frame.setTimestamp((double)d.frame->pkt_pts/1000.0); //qInfo() << "### SETPTS:" << d.frame->pkt_pts << d.frame->pts << "DTS:" << d.frame->pkt_dts << __FUNCTION__; frame.setPTS((double)d.frame->pkt_pts/1000.0); frame.setDuration(((double)d.frame->pkt_duration)/1000.0); // 2024/02/07 추가 KEYFRAME 도 추가가능.. frame.setMetaData(QStringLiteral("avbuf"), QVariant::fromValue(AVFrameBuffersRef(new AVFrameBuffers(d.frame)))); d.updateColorDetails(&frame); if (frame.format().hasPalette()) { frame.setMetaData(QStringLiteral("pallete"), QByteArray((const char*)d.frame->data[1], 256*4)); } return frame; } #else VideoFrame VideoDecoderFFmpeg::frame() { DPTR_D(VideoDecoderFFmpeg); if (!d.codec_ctx) return VideoFrame(); if(d.frame->width == 0) { d.frame->width = 1280; d.frame->height = 720; d.frame->linesize[0] = 1280; d.frame->linesize[1] = 640; d.frame->linesize[2] = 640; } //qDebug() << "format" << (int)(d.codec_ctx->pix_fmt) << "width" << d.frame->width << "height" << d.frame->height; VideoFrame frame(d.frame->width, d.frame->height, VideoFormat((int)0)); // VideoFormat::Format_YUV420P // it's safe if width, height, pixfmt will not change, only data change // VideoFrame frame(d.frame->width, d.frame->height, VideoFormat((int)d.codec_ctx->pix_fmt)); frame.setDisplayAspectRatio(d.getDAR(d.frame)); frame.setBits(d.frame->data); frame.setBytesPerLine(d.frame->linesize); // in s. TODO: what about AVFrame.pts? av_frame_get_best_effort_timestamp? move to VideoFrame::from(AVFrame*) frame.setTimestamp((double)d.frame->pkt_pts/1000.0); frame.setMetaData(QStringLiteral("avbuf"), QVariant::fromValue(AVFrameBuffersRef(new AVFrameBuffers(d.frame)))); d.updateColorDetails(&frame); if (frame.format().hasPalette()) { frame.setMetaData(QStringLiteral("pallete"), QByteArray((const char*)d.frame->data[1], 256*4)); } return frame; } #endif void VideoDecoderFFmpeg::setSkipLoopFilter(DiscardType value) { DPTR_D(VideoDecoderFFmpeg); d.skip_loop_filter = value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "skip_loop_filter", (int64_t)value, 0); } VideoDecoderFFmpeg::DiscardType VideoDecoderFFmpeg::skipLoopFilter() const { return (DiscardType)d_func().skip_loop_filter; } void VideoDecoderFFmpeg::setSkipIDCT(DiscardType value) { DPTR_D(VideoDecoderFFmpeg); d.skip_idct = (int)value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "skip_idct", (int64_t)value, 0); } VideoDecoderFFmpeg::DiscardType VideoDecoderFFmpeg::skipIDCT() const { return (DiscardType)d_func().skip_idct; } void VideoDecoderFFmpeg::setStrict(StrictType value) { DPTR_D(VideoDecoderFFmpeg); d.strict = (int)value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "strict", int64_t(value), 0); } VideoDecoderFFmpeg::StrictType VideoDecoderFFmpeg::strict() const { return (StrictType)d_func().strict; } void VideoDecoderFFmpeg::setSkipFrame(DiscardType value) { DPTR_D(VideoDecoderFFmpeg); d.skip_frame = (int)value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "skip_frame", (int64_t)value, 0); } VideoDecoderFFmpeg::DiscardType VideoDecoderFFmpeg::skipFrame() const { return (DiscardType)d_func().skip_frame; } void VideoDecoderFFmpeg::setThreads(int value) { DPTR_D(VideoDecoderFFmpeg); d.threads = value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "threads", (int64_t)value, 0); } int VideoDecoderFFmpeg::threads() const { return d_func().threads; } void VideoDecoderFFmpeg::setThreadFlags(ThreadFlags value) { DPTR_D(VideoDecoderFFmpeg); d.thread_type = (int)value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "thread_type", (int64_t)value, 0); } VideoDecoderFFmpeg::ThreadFlags VideoDecoderFFmpeg::threadFlags() const { return (ThreadFlags)d_func().thread_type; } void VideoDecoderFFmpeg::setMotionVectorVisFlags(MotionVectorVisFlags value) { DPTR_D(VideoDecoderFFmpeg); d.debug_mv = (int)value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "vismv", (int64_t)value, 0); } VideoDecoderFFmpeg::MotionVectorVisFlags VideoDecoderFFmpeg::motionVectorVisFlags() const { return (MotionVectorVisFlags)d_func().debug_mv; } void VideoDecoderFFmpeg::setBugFlags(BugFlags value) { DPTR_D(VideoDecoderFFmpeg); d.bug = (int)value; if (d.codec_ctx) av_opt_set_int(d.codec_ctx, "bug", (int64_t)value, 0); } VideoDecoderFFmpeg::BugFlags VideoDecoderFFmpeg::bugFlags() const { return (BugFlags)d_func().bug; } #if !(REMOVE_FFMPEG_HW) void VideoDecoderFFmpeg::setHwaccel(const QString &value) { DPTR_D(VideoDecoderFFmpeg); if (d.hwa == value) return; d.hwa = value.toLower(); Q_EMIT hwaccelChanged(); } QString VideoDecoderFFmpeg::hwaccel() const { return d_func().hwa; } #endif void i18n() { "codecName"; "skip_loop_filter"; "skip_idct"; "strict"; "skip_frame"; "threads"; "thread_type"; "vismv"; "bug"; } } //namespace FAV #include "VideoDecoderFFmpeg.moc"