/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * This file is part of QtAV This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ******************************************************************************/ #include "AVDecoder.h" #include "AVDecoder_p.h" #include "version.h" #include "internal.h" #include "Logger.h" namespace FAV { static AVCodec* get_codec(const QString &name, const QString& hwa, AVCodecID cid) { QString fullname(name); if (name.isEmpty()) { if (hwa.isEmpty()) return avcodec_find_decoder(cid); fullname = QString("%1_%2").arg(avcodec_get_name(cid)).arg(hwa); } AVCodec *codec = avcodec_find_decoder_by_name(fullname.toUtf8().constData()); if (codec) return codec; const AVCodecDescriptor* cd = avcodec_descriptor_get_by_name(fullname.toUtf8().constData()); if (cd) return avcodec_find_decoder(cd->id); return NULL; } AVDecoder::AVDecoder(AVDecoderPrivate &d) :DPTR_INIT(&d) { avcodec_register_all(); // avcodec_find_decoder will always be used } AVDecoder::~AVDecoder() { setCodecContext(0); // FIXME: will call virtual } QString AVDecoder::name() const { return QString(); } QString AVDecoder::description() const { return QString(); } bool AVDecoder::open() { DPTR_D(AVDecoder); // codec_ctx can't be null for none-ffmpeg based decoders because we may use it's properties in those decoders if (!d.codec_ctx) { qWarning("FFmpeg codec context not ready"); return false; } const QString hwa = property("hwaccel").toString(); AVCodec* codec = get_codec(codecName(), hwa, d.codec_ctx->codec_id); if (!codec) { // TODO: can be null for none-ffmpeg based decoders QString es("No codec could be found for '%1'"); if (d.codec_name.isEmpty()) { es = es.arg(QLatin1String(avcodec_get_name(d.codec_ctx->codec_id))); if (!hwa.isEmpty()) es.append('_').append(hwa); } else { es = es.arg(d.codec_name); } qWarning() << es; AVError::ErrorCode ec(AVError::CodecError); switch (d.codec_ctx->codec_type) { case AVMEDIA_TYPE_VIDEO: ec = AVError::VideoCodecNotFound; break; case AVMEDIA_TYPE_AUDIO: ec = AVError::AudioCodecNotFound; break; case AVMEDIA_TYPE_SUBTITLE: ec = AVError::SubtitleCodecNotFound; default: break; } Q_EMIT error(AVError(ec, es)); return false; } // hwa extra init can be here if (!d.open()) { d.close(); return false; } // CODEC_FLAG_OUTPUT_CORRUPT, CODEC_FLAG2_SHOW_ALL? // TODO: skip for none-ffmpeg based decoders d.applyOptionsForDict(); av_opt_set_int(d.codec_ctx, "refcounted_frames", d.enableFrameRef(), 0); // why dict may have no effect? d.codec_ctx->thread_count = 1; d.codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; d.codec_ctx->flags2 |= AV_CODEC_FLAG2_CHUNKS; // TODO: only open for ff decoders //av_dict_set(&d.dict, "lowres", "1", 0); // dict is used for a specified AVCodec options (priv_class), av_opt_set_xxx(avctx) is only for avctx AV_ENSURE_OK(avcodec_open2(d.codec_ctx, codec, d.options.isEmpty() ? NULL : &d.dict), false); d.is_open = true; static const char* thread_name[] = { "Single", "Frame", "Slice"}; qDebug("%s thread type: %s, count: %d", metaObject()->className(), thread_name[d.codec_ctx->active_thread_type], d.codec_ctx->thread_count); return true; } bool AVDecoder::close() { if (!isOpen()) { return true; } DPTR_D(AVDecoder); d.is_open = false; // hwa extra finalize can be here flush(); d.close(); // TODO: reset config? if (d.codec_ctx) { AV_ENSURE_OK(avcodec_close(d.codec_ctx), false); } return true; } bool AVDecoder::isOpen() const { return d_func().is_open; } void AVDecoder::flush() { if (!isAvailable()) return; if (!isOpen()) return; avcodec_flush_buffers(d_func().codec_ctx); } /* * do nothing if equal * close the old one. the codec context can not be shared in more than 1 decoder. */ void AVDecoder::setCodecContext(void *codecCtx) { DPTR_D(AVDecoder); AVCodecContext *ctx = (AVCodecContext*)codecCtx; if (ctx == NULL || d.codec_ctx == ctx) { return; } if (isOpen()) { #if !(OFF_OTHER_DEBUG) qWarning("Can not copy codec properties when it's open"); #endif close(); // } d.is_open = false; if (!ctx) { avcodec_free_context(&d.codec_ctx); d.codec_ctx = 0; return; } if (!d.codec_ctx) d.codec_ctx = avcodec_alloc_context3(NULL); // avcodec_alloc_context3(codec) equals to avcodec_alloc_context3(NULL) + avcodec_get_context_defaults3(codec), codec specified private data is initialized if (!d.codec_ctx) { qWarning("avcodec_alloc_context3 failed"); return; } AV_ENSURE_OK(avcodec_copy_context(d.codec_ctx, ctx)); } //TODO: reset other parameters? void* AVDecoder::codecContext() const { return d_func().codec_ctx; } void AVDecoder::setCodecName(const QString &name) { DPTR_D(AVDecoder); if (d.codec_name == name) return; d.codec_name = name; Q_EMIT codecNameChanged(); } QString AVDecoder::codecName() const { DPTR_D(const AVDecoder); return d.codec_name; } bool AVDecoder::isAvailable() const { return d_func().codec_ctx != 0; } int AVDecoder::undecodedSize() const { return d_func().undecoded_size; } void AVDecoder::setOptions(const QVariantHash &dict) { DPTR_D(AVDecoder); d.options = dict; // if dict is empty, can not return here, default options will be set for AVCodecContext // apply to AVCodecContext d.applyOptionsForContext(); /* set AVDecoder meta properties. * we do not check whether the property exists thus we can set dynamic properties. */ if (dict.isEmpty()) return; if (name() == QLatin1String("avcodec")) return; QVariant opt(dict); if (dict.contains(name())) opt = dict.value(name()); else if (dict.contains(name().toLower())) opt = dict.value(name().toLower()); Internal::setOptionsForQObject(opt, this); } QVariantHash AVDecoder::options() const { return d_func().options; } void AVDecoderPrivate::applyOptionsForDict() { if (dict) { av_dict_free(&dict); dict = 0; //aready 0 in av_free } // enable ref if possible av_dict_set(&dict, "refcounted_frames", enableFrameRef() ? "1" : "0", 0); if (options.isEmpty()) return; // TODO: use QVariantMap only if (!options.contains(QStringLiteral("avcodec"))) return; qDebug("set AVCodecContext dict:"); // workaround for VideoDecoderFFmpeg. now it does not call av_opt_set_xxx, so set here in dict // TODO: wrong if opt is empty Internal::setOptionsToDict(options.value(QStringLiteral("avcodec")), &dict); } void AVDecoderPrivate::applyOptionsForContext() { if (!codec_ctx) return; if (options.isEmpty()) { // av_opt_set_defaults(codec_ctx); //can't set default values! result maybe unexpected return; } if (!options.contains(QStringLiteral("avcodec"))) return; // workaround for VideoDecoderFFmpeg. now it does not call av_opt_set_xxx, so set here in dict // TODO: wrong if opt is empty Internal::setOptionsToFFmpegObj(options.value(QStringLiteral("avcodec")), codec_ctx); } } //namespace FAV