Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qwindowsformatinfo_p.h"
++
++ #include <QtMultimedia/private/qcomtaskresource_p.h>
++ #include <QtMultimedia/private/qwindowsmultimediautils_p.h>
++ #include <QtGui/qimagewriter.h>
++ #include <QtCore/qhash.h>
++ #include <QtCore/qlist.h>
++ #include <QtCore/qset.h>
++ #include <QtCore/private/qcomptr_p.h>
++
++ #include <mfapi.h>
++ #include <mftransform.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ namespace {
++
++ template<typename T>
++ using CheckedCodecs = QHash<std::pair<T, QMediaFormat::ConversionMode>, bool>;
++
++ bool isSupportedMFT(const GUID &category, const MFT_REGISTER_TYPE_INFO &type, QMediaFormat::ConversionMode mode)
++ {
++ UINT32 count = 0;
++ QComTaskResource<IMFActivate *> activate;
++ HRESULT hr = E_FAIL;
++
++ // MFTEnumEx may crash inside third-party drivers (e.g. D3D12Core.dll,
++ // RTWorkQ.dll) when enumerating hardware codecs. Wrap the call with SEH
++ // to prevent the crash from taking down the whole application.
++ #ifdef Q_CC_MSVC
++ __try {
++ #endif
++ hr = MFTEnumEx(
++ category,
++ MFT_ENUM_FLAG_ALL,
++ (mode == QMediaFormat::Encode) ? nullptr : &type, // Input type
++ (mode == QMediaFormat::Encode) ? &type : nullptr, // Output type
++ activate.address(),
++ &count
++ );
++ #ifdef Q_CC_MSVC
++ } __except (EXCEPTION_EXECUTE_HANDLER) {
++ qWarning("MFTEnumEx crashed while enumerating codecs (SEH exception caught)");
++ return false;
++ }
++ #endif
++
++ return SUCCEEDED(hr) && count > 0;
++ }
++
++ bool isSupportedCodec(QMediaFormat::AudioCodec codec, QMediaFormat::ConversionMode mode)
++ {
++ return isSupportedMFT((mode == QMediaFormat::Encode) ? MFT_CATEGORY_AUDIO_ENCODER : MFT_CATEGORY_AUDIO_DECODER,
++ { MFMediaType_Audio, QWMF::audioFormatForCodec(codec) },
++ mode);
++ }
++
++ bool isSupportedCodec(QMediaFormat::VideoCodec codec, QMediaFormat::ConversionMode mode)
++ {
++ return isSupportedMFT((mode == QMediaFormat::Encode) ? MFT_CATEGORY_VIDEO_ENCODER : MFT_CATEGORY_VIDEO_DECODER,
++ { MFMediaType_Video, QWMF::videoFormatForCodec(codec) },
++ mode);
++ }
++
++ template <typename T>
++ bool isSupportedCodec(T codec, QMediaFormat::ConversionMode m, CheckedCodecs<T> &checkedCodecs)
++ {
++ if (auto it = checkedCodecs.constFind(std::pair{codec, m}); it != checkedCodecs.constEnd())
++ return it.value();
++
++ const bool supported = isSupportedCodec(codec, m);
++
++ checkedCodecs.insert(std::pair{codec, m}, supported);
++ return supported;
++ }
++
++ }
++
++ static QList<QImageCapture::FileFormat> getImageFormatList()
++ {
++ QList<QImageCapture::FileFormat> list;
++ const auto formats = QImageWriter::supportedImageFormats();
++
++ for (const auto &f : formats) {
++ auto format = QString::fromUtf8(f);
++ if (format.compare(u"jpg", Qt::CaseInsensitive) == 0)
++ list.append(QImageCapture::FileFormat::JPEG);
++ else if (format.compare(u"png", Qt::CaseInsensitive) == 0)
++ list.append(QImageCapture::FileFormat::PNG);
++ else if (format.compare(u"webp", Qt::CaseInsensitive) == 0)
++ list.append(QImageCapture::FileFormat::WebP);
++ else if (format.compare(u"tiff", Qt::CaseInsensitive) == 0)
++ list.append(QImageCapture::FileFormat::Tiff);
++ }
++
++ return list;
++ }
++
++ QWindowsFormatInfo::QWindowsFormatInfo()
++ {
++ const QList<CodecMap> containerTable = {
++ { QMediaFormat::MPEG4,
++ { QMediaFormat::AudioCodec::AAC, QMediaFormat::AudioCodec::MP3, QMediaFormat::AudioCodec::ALAC, QMediaFormat::AudioCodec::AC3, QMediaFormat::AudioCodec::EAC3 },
++ { QMediaFormat::VideoCodec::H264, QMediaFormat::VideoCodec::H265, QMediaFormat::VideoCodec::MotionJPEG } },
++ { QMediaFormat::Matroska,
++ { QMediaFormat::AudioCodec::AAC, QMediaFormat::AudioCodec::MP3, QMediaFormat::AudioCodec::ALAC, QMediaFormat::AudioCodec::AC3, QMediaFormat::AudioCodec::EAC3, QMediaFormat::AudioCodec::FLAC, QMediaFormat::AudioCodec::Vorbis, QMediaFormat::AudioCodec::Opus },
++ { QMediaFormat::VideoCodec::H264, QMediaFormat::VideoCodec::H265, QMediaFormat::VideoCodec::VP8, QMediaFormat::VideoCodec::VP9, QMediaFormat::VideoCodec::MotionJPEG } },
++ { QMediaFormat::WebM,
++ { QMediaFormat::AudioCodec::Vorbis, QMediaFormat::AudioCodec::Opus },
++ { QMediaFormat::VideoCodec::VP8, QMediaFormat::VideoCodec::VP9 } },
++ { QMediaFormat::QuickTime,
++ { QMediaFormat::AudioCodec::AAC, QMediaFormat::AudioCodec::MP3, QMediaFormat::AudioCodec::ALAC, QMediaFormat::AudioCodec::AC3, QMediaFormat::AudioCodec::EAC3 },
++ { QMediaFormat::VideoCodec::H264, QMediaFormat::VideoCodec::H265, QMediaFormat::VideoCodec::MotionJPEG } },
++ { QMediaFormat::AAC,
++ { QMediaFormat::AudioCodec::AAC },
++ {} },
++ { QMediaFormat::MP3,
++ { QMediaFormat::AudioCodec::MP3 },
++ {} },
++ { QMediaFormat::FLAC,
++ { QMediaFormat::AudioCodec::FLAC },
++ {} },
++ { QMediaFormat::Mpeg4Audio,
++ { QMediaFormat::AudioCodec::AAC, QMediaFormat::AudioCodec::MP3, QMediaFormat::AudioCodec::ALAC, QMediaFormat::AudioCodec::AC3, QMediaFormat::AudioCodec::EAC3 },
++ {} },
++ { QMediaFormat::WMA,
++ { QMediaFormat::AudioCodec::WMA },
++ {} },
++ { QMediaFormat::WMV,
++ { QMediaFormat::AudioCodec::WMA },
++ { QMediaFormat::VideoCodec::WMV } }
++ };
++
++ const QSet<QMediaFormat::FileFormat> decoderFormats = {
++ QMediaFormat::MPEG4,
++ QMediaFormat::Matroska,
++ QMediaFormat::WebM,
++ QMediaFormat::QuickTime,
++ QMediaFormat::AAC,
++ QMediaFormat::MP3,
++ QMediaFormat::FLAC,
++ QMediaFormat::Mpeg4Audio,
++ QMediaFormat::WMA,
++ QMediaFormat::WMV,
++ };
++
++ const QSet<QMediaFormat::FileFormat> encoderFormats = {
++ QMediaFormat::MPEG4,
++ QMediaFormat::AAC,
++ QMediaFormat::MP3,
++ QMediaFormat::FLAC,
++ QMediaFormat::Mpeg4Audio,
++ QMediaFormat::WMA,
++ QMediaFormat::WMV,
++ };
++
++ CheckedCodecs<QMediaFormat::AudioCodec> checkedAudioCodecs;
++ CheckedCodecs<QMediaFormat::VideoCodec> checkedVideoCodecs;
++
++ auto ensureCodecs = [&] (CodecMap &codecs, QMediaFormat::ConversionMode mode) {
++ codecs.audio.removeIf([&] (auto codec) { return !isSupportedCodec(codec, mode, checkedAudioCodecs); });
++ codecs.video.removeIf([&] (auto codec) { return !isSupportedCodec(codec, mode, checkedVideoCodecs); });
++ return !codecs.video.empty() || !codecs.audio.empty();
++ };
++
++ for (const auto &codecMap : containerTable) {
++ if (decoderFormats.contains(codecMap.format)) {
++ auto m = codecMap;
++ if (ensureCodecs(m, QMediaFormat::Decode))
++ decoders.append(m);
++ }
++
++ if (encoderFormats.contains(codecMap.format)) {
++ auto m = codecMap;
++ if (ensureCodecs(m, QMediaFormat::Encode))
++ encoders.append(m);
++ }
++ }
++
++ imageFormats = getImageFormatList();
++
++ fixupCodecMaps();
++ }
++
++ QWindowsFormatInfo::~QWindowsFormatInfo()
++ {
++ }
++
++ QT_END_NAMESPACE