Files
fmviewer3/project/fm_viewer/fav/AVDecoder.cpp
2026-02-21 17:11:31 +09:00

288 lines
8.3 KiB
C++

/******************************************************************************
QtAV: Multimedia framework based on Qt and FFmpeg
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
* 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