diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 93253d6dc..12de42f3b 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -16,10 +16,6 @@ endif() find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat) find_package(CURL REQUIRED) -if(NOT TARGET OBS::libff-util) - add_subdirectory("${CMAKE_SOURCE_DIR}/deps/libff" "${CMAKE_BINARY_DIR}/deps/libff") -endif() - if(NOT TARGET OBS::json11) add_subdirectory("${CMAKE_SOURCE_DIR}/deps/json11" "${CMAKE_BINARY_DIR}/deps/json11") endif() @@ -35,7 +31,6 @@ target_link_libraries( FFmpeg::avformat OBS::libobs OBS::frontend-api - OBS::libff-util OBS::json11) include(cmake/ui-qt.cmake) @@ -71,6 +66,8 @@ target_sources( auth-oauth.cpp auth-oauth.hpp display-helpers.hpp + ffmpeg-utils.cpp + ffmpeg-utils.hpp multiview.cpp multiview.hpp obf.c diff --git a/UI/cmake/legacy.cmake b/UI/cmake/legacy.cmake index 99d4728d9..d55219ea4 100644 --- a/UI/cmake/legacy.cmake +++ b/UI/cmake/legacy.cmake @@ -150,10 +150,10 @@ target_sources( ui-validation.hpp multiview.cpp multiview.hpp + ffmpeg-utils.cpp + ffmpeg-utils.hpp ${CMAKE_SOURCE_DIR}/deps/json11/json11.cpp ${CMAKE_SOURCE_DIR}/deps/json11/json11.hpp - ${CMAKE_SOURCE_DIR}/deps/libff/libff/ff-util.c - ${CMAKE_SOURCE_DIR}/deps/libff/libff/ff-util.h ${CMAKE_CURRENT_BINARY_DIR}/ui-config.h) target_sources( @@ -289,7 +289,7 @@ target_sources(obs PRIVATE importers/importers.cpp importers/importers.hpp impor target_compile_features(obs PRIVATE cxx_std_17) -target_include_directories(obs PRIVATE ${CMAKE_SOURCE_DIR}/deps/json11 ${CMAKE_SOURCE_DIR}/deps/libff) +target_include_directories(obs PRIVATE ${CMAKE_SOURCE_DIR}/deps/json11) target_link_libraries(obs PRIVATE CURL::libcurl FFmpeg::avcodec FFmpeg::avutil FFmpeg::avformat OBS::libobs OBS::frontend-api) @@ -374,9 +374,6 @@ if(OS_WINDOWS) if(MSVC) target_link_options(obs PRIVATE "LINKER:/IGNORE:4098" "LINKER:/IGNORE:4099") target_link_libraries(obs PRIVATE OBS::w32-pthreads) - - set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}../deps/libff/libff/ff-util.c PROPERTIES COMPILE_FLAGS - -Dinline=__inline) endif() if(CMAKE_SIZEOF_VOID_P EQUAL 4) diff --git a/UI/ffmpeg-utils.cpp b/UI/ffmpeg-utils.cpp new file mode 100644 index 000000000..a59d39ea5 --- /dev/null +++ b/UI/ffmpeg-utils.cpp @@ -0,0 +1,290 @@ +/****************************************************************************** + Copyright (C) 2023 by Dennis Sädtler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "ffmpeg-utils.hpp" + +#include +#include + +extern "C" { +#include +} + +using namespace std; + +static void GetCodecsForId(const FFmpegFormat &format, + vector &codecs, enum AVCodecID id, + bool ignore_compaibility) +{ + + const AVCodec *codec = nullptr; + void *i = 0; + + while ((codec = av_codec_iterate(&i)) != nullptr) { + if (codec->id != id) + continue; + // Not an encoding codec + if (!av_codec_is_encoder(codec)) + continue; + // Skip if not supported and compatibility check not disabled + if (!ignore_compaibility && + !av_codec_get_tag(format.codec_tags, codec->id)) { + continue; + } + + FFmpegCodec d{codec->name, codec->long_name, codec->id}; + + const AVCodec *base_codec = avcodec_find_encoder(codec->id); + if (strcmp(base_codec->name, codec->name) != 0) { + d.alias = true; + d.base_name = base_codec->name; + } + + switch (codec->type) { + case AVMEDIA_TYPE_AUDIO: + d.type = FFmpegCodecType::AUDIO; + break; + case AVMEDIA_TYPE_VIDEO: + d.type = FFmpegCodecType::VIDEO; + break; + default: + d.type = FFmpegCodecType::UNKNOWN; + } + + codecs.push_back(d); + } +} + +static std::vector GetCodecDescriptors() +{ + std::vector codecs; + + const AVCodecDescriptor *desc = nullptr; + while ((desc = avcodec_descriptor_next(desc)) != nullptr) + codecs.push_back(desc); + + return codecs; +} + +vector GetFormatCodecs(const FFmpegFormat &format, + bool ignore_compatibility) +{ + vector codecs; + auto codecDescriptors = GetCodecDescriptors(); + + if (codecDescriptors.empty()) + return codecs; + + for (const AVCodecDescriptor *codec : codecDescriptors) + GetCodecsForId(format, codecs, codec->id, ignore_compatibility); + + return codecs; +} + +static inline bool is_output_device(const AVClass *avclass) +{ + if (!avclass) + return 0; + + switch (avclass->category) { + case AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT: + case AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT: + case AV_CLASS_CATEGORY_DEVICE_OUTPUT: + return true; + default: + return false; + } +} + +vector GetSupportedFormats() +{ + vector formats; + const AVOutputFormat *output_format; + + void *i = 0; + while ((output_format = av_muxer_iterate(&i)) != nullptr) { + if (is_output_device(output_format->priv_class)) + continue; + + formats.push_back({ + output_format->name, + output_format->long_name, + output_format->mime_type, + output_format->extensions, + output_format->audio_codec, + output_format->video_codec, + output_format->codec_tag, + }); + } + + return formats; +} + +static const char *get_encoder_name(const char *format_name, + enum AVCodecID codec_id) +{ + const AVCodec *codec = avcodec_find_encoder(codec_id); + if (codec == nullptr && codec_id == AV_CODEC_ID_NONE) + return nullptr; + else if (codec == nullptr) + return format_name; + else + return codec->name; +} + +const char *FFmpegFormat::GetDefaultName(FFmpegCodecType codec_type) const +{ + + switch (codec_type) { + case FFmpegCodecType::AUDIO: + return get_encoder_name(name, audio_codec); + case FFmpegCodecType::VIDEO: + return get_encoder_name(name, video_codec); + default: + return nullptr; + } +} + +bool FFCodecAndFormatCompatible(const char *codec, const char *format) +{ + if (!codec || !format) + return false; + +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(59, 0, 100) + AVOutputFormat *output_format; +#else + const AVOutputFormat *output_format; +#endif + output_format = av_guess_format(format, NULL, NULL); + if (!output_format) + return false; + + const AVCodecDescriptor *codec_desc = + avcodec_descriptor_get_by_name(codec); + if (!codec_desc) + return false; + +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 0, 100) + return avformat_query_codec(output_format, codec_desc->id, + FF_COMPLIANCE_EXPERIMENTAL) == 1; +#else + return avformat_query_codec(output_format, codec_desc->id, + FF_COMPLIANCE_NORMAL) == 1; +#endif +} + +static const unordered_set builtin_codecs = { + "h264", "hevc", "av1", "prores", "aac", "opus", + "alac", "flac", "pcm_s16le", "pcm_s24le", "pcm_f32le", +}; + +bool IsBuiltinCodec(const char *codec) +{ + return builtin_codecs.count(codec) > 0; +} + +static const unordered_map> codec_compat = { + // Technically our muxer supports HEVC and AV1 as well, but nothing else does + {"flv", + { + "h264", + "aac", + }}, + {"mpegts", + { + "h264", + "hevc", + "aac", + "opus", + }}, + {"hls", + // Also using MPEG-TS in our case, but no Opus support + { + "h264", + "hevc", + "aac", + }}, + {"mp4", + { + "h264", + "hevc", + "av1", + "aac", + "opus", + "alac", + "flac", +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 5, 100) + // PCM in MP4 is only supported in FFmpeg > 6.0 + "pcm_s16le", + "pcm_s24le", + "pcm_f32le", +#endif + }}, + {"fragmented_mp4", + { + "h264", + "hevc", + "av1", + "aac", + "opus", + "alac", + "flac", +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 5, 100) + "pcm_s16le", + "pcm_s24le", + "pcm_f32le", +#endif + }}, + {"mov", + { + "h264", + "hevc", + "prores", + "aac", + "alac", + "pcm_s16le", + "pcm_s24le", + "pcm_f32le", + }}, + {"fragmented_mov", + { + "h264", + "hevc", + "prores", + "aac", + "alac", + "pcm_s16le", + "pcm_s24le", + "pcm_f32le", + }}, + // MKV supports everything + {"mkv", {}}, +}; + +bool ContainerSupportsCodec(const string &container, const string &codec) +{ + auto iter = codec_compat.find(container); + if (iter == codec_compat.end()) + return false; + + auto codecs = iter->second; + // Assume everything is supported + if (codecs.empty()) + return true; + + return codecs.count(codec) > 0; +} diff --git a/UI/ffmpeg-utils.hpp b/UI/ffmpeg-utils.hpp new file mode 100644 index 000000000..5a5eeadf7 --- /dev/null +++ b/UI/ffmpeg-utils.hpp @@ -0,0 +1,84 @@ +/****************************************************************************** + Copyright (C) 2023 by Dennis Sädtler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include + +extern "C" { +#include +#include +} + +enum FFmpegCodecType { AUDIO, VIDEO, UNKNOWN }; + +struct FFmpegFormat { + const char *name; + const char *long_name; + const char *mime_type; + const char *extensions; + AVCodecID audio_codec; + AVCodecID video_codec; + const AVCodecTag *const *codec_tags; + + FFmpegFormat() = default; + + const char *GetDefaultName(FFmpegCodecType codec_type) const; + + bool HasAudio() const { return audio_codec != AV_CODEC_ID_NONE; } + bool HasVideo() const { return video_codec != AV_CODEC_ID_NONE; } + + bool operator==(const FFmpegFormat &format) const + { + if (strcmp(name, format.name) != 0) + return false; + return strcmp(mime_type, format.mime_type) != 0; + } +}; +Q_DECLARE_METATYPE(FFmpegFormat) + +struct FFmpegCodec { + const char *name; + const char *long_name; + int id; + + bool alias; + const char *base_name; + + FFmpegCodecType type; + + FFmpegCodec() = default; + + bool operator==(const FFmpegCodec &codec) const + { + if (id != codec.id) + return false; + return strcmp(name, codec.name) != 0; + } +}; +Q_DECLARE_METATYPE(FFmpegCodec) + +std::vector GetSupportedFormats(); +std::vector GetFormatCodecs(const FFmpegFormat &format, + bool ignore_compatibility); + +bool FFCodecAndFormatCompatible(const char *codec, const char *format); +bool IsBuiltinCodec(const char *codec); +bool ContainerSupportsCodec(const std::string &container, + const std::string &codec); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 8e458e9b2..b12735369 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -7585,17 +7585,22 @@ void OBSBasic::AutoRemux(QString input, bool no_show) config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); bool audio_is_pcm = strncmp(aCodecName, "pcm", 3) == 0; + +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 5, 100) /* FFmpeg <= 6.0 cannot remux AV1+PCM into any supported format. */ - if (audio_is_pcm && !ff_supports_pcm_in_mp4() && - strcmp(vCodecName, "av1") == 0) + if (audio_is_pcm && strcmp(vCodecName, "av1") == 0) return; +#endif /* Retain original container for fMP4/fMOV */ if (strncmp(format, "fragmented", 10) == 0) { output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0 || - (audio_is_pcm && !ff_supports_pcm_in_mp4())) { + } else if (strcmp(vCodecName, "prores") == 0) { output += "mov"; +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 5, 100) + } else if (audio_is_pcm) { + output += "mov"; +#endif } else { output += "mp4"; } diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 3630e95f5..dc9f051fc 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -83,51 +83,6 @@ protected: } }; -// Used for QVariant in codec comboboxes -namespace { -static bool StringEquals(QString left, QString right) -{ - return left == right; -} -struct FormatDesc { - const char *name = nullptr; - const char *mimeType = nullptr; - const ff_format_desc *desc = nullptr; - - inline FormatDesc() = default; - inline FormatDesc(const char *name, const char *mimeType, - const ff_format_desc *desc = nullptr) - : name(name), - mimeType(mimeType), - desc(desc) - { - } - - bool operator==(const FormatDesc &f) const - { - if (!StringEquals(name, f.name)) - return false; - return StringEquals(mimeType, f.mimeType); - } -}; -struct CodecDesc { - const char *name = nullptr; - int id = 0; - - inline CodecDesc() = default; - inline CodecDesc(const char *name, int id) : name(name), id(id) {} - - bool operator==(const CodecDesc &codecDesc) const - { - if (id != codecDesc.id) - return false; - return StringEquals(name, codecDesc.name); - } -}; -} -Q_DECLARE_METATYPE(FormatDesc) -Q_DECLARE_METATYPE(CodecDesc) - static inline bool ResTooHigh(uint32_t cx, uint32_t cy) { return cx > 16384 || cy > 16384; @@ -231,36 +186,35 @@ static inline QString GetComboData(QComboBox *combo) static int FindEncoder(QComboBox *combo, const char *name, int id) { - CodecDesc codecDesc(name, id); + FFmpegCodec codec{name, nullptr, id}; + for (int i = 0; i < combo->count(); i++) { QVariant v = combo->itemData(i); if (!v.isNull()) { - if (codecDesc == v.value()) { + if (codec == v.value()) { return i; - break; } } } return -1; } -static CodecDesc GetDefaultCodecDesc(const ff_format_desc *formatDesc, - ff_codec_type codecType) +static FFmpegCodec GetDefaultCodec(const FFmpegFormat &format, + FFmpegCodecType codecType) { int id = 0; switch (codecType) { - case FF_CODEC_AUDIO: - id = ff_format_desc_audio(formatDesc); + case AUDIO: + id = format.audio_codec; break; - case FF_CODEC_VIDEO: - id = ff_format_desc_video(formatDesc); + case VIDEO: + id = format.video_codec; break; default: - return CodecDesc(); + return FFmpegCodec(); } - return CodecDesc(ff_format_desc_get_default_name(formatDesc, codecType), - id); + return FFmpegCodec{format.GetDefaultName(codecType), nullptr, id}; } #define INVALID_BITRATE 10000 @@ -754,9 +708,6 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) //Apply button disabled until change. EnableApplyButton(false); - // Initialize libff library - ff_init(); - installEventFilter(new SettingsEventFilter()); LoadColorRanges(); @@ -1140,25 +1091,21 @@ void OBSBasicSettings::LoadFormats() #define FORMAT_STR(str) QTStr("Basic.Settings.Output.Format." str) ui->advOutFFFormat->blockSignals(true); - formats.reset(ff_format_supported()); - const ff_format_desc *format = formats.get(); + formats = GetSupportedFormats(); + + for (auto &format : formats) { + bool audio = format.HasAudio(); + bool video = format.HasVideo(); - while (format != nullptr) { - bool audio = ff_format_desc_has_audio(format); - bool video = ff_format_desc_has_video(format); - FormatDesc formatDesc(ff_format_desc_name(format), - ff_format_desc_mime_type(format), format); if (audio || video) { - QString itemText(ff_format_desc_name(format)); + QString itemText(format.name); if (audio ^ video) itemText += QString(" (%1)").arg( audio ? AUDIO_STR : VIDEO_STR); ui->advOutFFFormat->addItem( - itemText, QVariant::fromValue(formatDesc)); + itemText, QVariant::fromValue(format)); } - - format = ff_format_desc_next(format); } ui->advOutFFFormat->model()->sort(0); @@ -1187,26 +1134,22 @@ void OBSBasicSettings::LoadFormats() #undef FORMAT_STR } -static void AddCodec(QComboBox *combo, const ff_codec_desc *codec_desc) +static void AddCodec(QComboBox *combo, const FFmpegCodec &codec) { - QString itemText(ff_codec_desc_name(codec_desc)); - if (ff_codec_desc_is_alias(codec_desc)) - itemText += QString(" (%1)").arg( - ff_codec_desc_base_name(codec_desc)); + QString itemText(codec.name); + if (codec.alias) + itemText += QString(" (%1)").arg(codec.base_name); - CodecDesc cd(ff_codec_desc_name(codec_desc), - ff_codec_desc_id(codec_desc)); - - combo->addItem(itemText, QVariant::fromValue(cd)); + combo->addItem(itemText, QVariant::fromValue(codec)); } #define AV_ENCODER_DEFAULT_STR \ QTStr("Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault") -static void AddDefaultCodec(QComboBox *combo, const ff_format_desc *formatDesc, - ff_codec_type codecType) +static void AddDefaultCodec(QComboBox *combo, const FFmpegFormat &format, + FFmpegCodecType codecType) { - CodecDesc cd = GetDefaultCodecDesc(formatDesc, codecType); + FFmpegCodec cd = GetDefaultCodec(format, codecType); int existingIdx = FindEncoder(combo, cd.name, cd.id); if (existingIdx >= 0) @@ -1219,48 +1162,41 @@ static void AddDefaultCodec(QComboBox *combo, const ff_format_desc *formatDesc, #define AV_ENCODER_DISABLE_STR \ QTStr("Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable") -void OBSBasicSettings::ReloadCodecs(const ff_format_desc *formatDesc) +void OBSBasicSettings::ReloadCodecs(const FFmpegFormat &format) { ui->advOutFFAEncoder->blockSignals(true); ui->advOutFFVEncoder->blockSignals(true); ui->advOutFFAEncoder->clear(); ui->advOutFFVEncoder->clear(); - if (formatDesc == nullptr) - return; + bool ignore_compatibility = ui->advOutFFIgnoreCompat->isChecked(); + vector supportedCodecs = + GetFormatCodecs(format, ignore_compatibility); - bool ignore_compatability = ui->advOutFFIgnoreCompat->isChecked(); - OBSFFCodecDesc codecDescs( - ff_codec_supported(formatDesc, ignore_compatability)); - - const ff_codec_desc *codec = codecDescs.get(); - - while (codec != nullptr) { - switch (ff_codec_desc_type(codec)) { - case FF_CODEC_AUDIO: + for (auto &codec : supportedCodecs) { + switch (codec.type) { + case AUDIO: AddCodec(ui->advOutFFAEncoder, codec); break; - case FF_CODEC_VIDEO: + case VIDEO: AddCodec(ui->advOutFFVEncoder, codec); break; default: break; } - - codec = ff_codec_desc_next(codec); } - if (ff_format_desc_has_audio(formatDesc)) - AddDefaultCodec(ui->advOutFFAEncoder, formatDesc, - FF_CODEC_AUDIO); - if (ff_format_desc_has_video(formatDesc)) - AddDefaultCodec(ui->advOutFFVEncoder, formatDesc, - FF_CODEC_VIDEO); + if (format.HasAudio()) + AddDefaultCodec(ui->advOutFFAEncoder, format, + FFmpegCodecType::AUDIO); + if (format.HasVideo()) + AddDefaultCodec(ui->advOutFFVEncoder, format, + FFmpegCodecType::VIDEO); ui->advOutFFAEncoder->model()->sort(0); ui->advOutFFVEncoder->model()->sort(0); - QVariant disable = QVariant::fromValue(CodecDesc()); + QVariant disable = QVariant::fromValue(FFmpegCodec()); ui->advOutFFAEncoder->insertItem(0, AV_ENCODER_DISABLE_STR, disable); ui->advOutFFVEncoder->insertItem(0, AV_ENCODER_DISABLE_STR, disable); @@ -2302,12 +2238,12 @@ void OBSBasicSettings::LoadAdvOutputRecordingEncoderProperties() static void SelectFormat(QComboBox *combo, const char *name, const char *mimeType) { - FormatDesc formatDesc(name, mimeType); + FFmpegFormat format{name, mimeType}; for (int i = 0; i < combo->count(); i++) { QVariant v = combo->itemData(i); if (!v.isNull()) { - if (formatDesc == v.value()) { + if (format == v.value()) { combo->setCurrentIndex(i); return; } @@ -2523,14 +2459,14 @@ void OBSBasicSettings::LoadOutputSettings() loading = false; } -void OBSBasicSettings::SetAdvOutputFFmpegEnablement(ff_codec_type encoderType, +void OBSBasicSettings::SetAdvOutputFFmpegEnablement(FFmpegCodecType encoderType, bool enabled, bool enableEncoder) { bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale"); switch (encoderType) { - case FF_CODEC_VIDEO: + case FFmpegCodecType::VIDEO: ui->advOutFFVBitrate->setEnabled(enabled); ui->advOutFFVGOPSize->setEnabled(enabled); ui->advOutFFUseRescale->setEnabled(enabled); @@ -2538,7 +2474,7 @@ void OBSBasicSettings::SetAdvOutputFFmpegEnablement(ff_codec_type encoderType, ui->advOutFFVEncoder->setEnabled(enabled || enableEncoder); ui->advOutFFVCfg->setEnabled(enabled); break; - case FF_CODEC_AUDIO: + case FFmpegCodecType::AUDIO: ui->advOutFFABitrate->setEnabled(enabled); ui->advOutFFAEncoder->setEnabled(enabled || enableEncoder); ui->advOutFFACfg->setEnabled(enabled); @@ -3766,13 +3702,13 @@ void OBSBasicSettings::SaveFormat(QComboBox *combo) { QVariant v = combo->currentData(); if (!v.isNull()) { - FormatDesc desc = v.value(); + auto format = v.value(); config_set_string(main->Config(), "AdvOut", "FFFormat", - desc.name); + format.name); config_set_string(main->Config(), "AdvOut", "FFFormatMimeType", - desc.mimeType); + format.mime_type); - const char *ext = ff_format_desc_extensions(desc.desc); + const char *ext = format.extensions; string extStr = ext ? ext : ""; char *comma = strchr(&extStr[0], ','); @@ -3795,9 +3731,10 @@ void OBSBasicSettings::SaveEncoder(QComboBox *combo, const char *section, const char *value) { QVariant v = combo->currentData(); - CodecDesc cd; + FFmpegCodec cd{}; if (!v.isNull()) - cd = v.value(); + cd = v.value(); + config_set_int(main->Config(), section, QT_TO_UTF8(QString("%1Id").arg(value)), cd.id); if (cd.id != 0) @@ -4437,27 +4374,29 @@ void OBSBasicSettings::on_advOutFFFormat_currentIndexChanged(int idx) const QVariant itemDataVariant = ui->advOutFFFormat->itemData(idx); if (!itemDataVariant.isNull()) { - FormatDesc desc = itemDataVariant.value(); - SetAdvOutputFFmpegEnablement( - FF_CODEC_AUDIO, ff_format_desc_has_audio(desc.desc), - false); - SetAdvOutputFFmpegEnablement( - FF_CODEC_VIDEO, ff_format_desc_has_video(desc.desc), - false); - ReloadCodecs(desc.desc); - ui->advOutFFFormatDesc->setText( - ff_format_desc_long_name(desc.desc)); + auto format = itemDataVariant.value(); + SetAdvOutputFFmpegEnablement(FFmpegCodecType::AUDIO, + format.HasAudio(), false); + SetAdvOutputFFmpegEnablement(FFmpegCodecType::VIDEO, + format.HasVideo(), false); + ReloadCodecs(format); - CodecDesc defaultAudioCodecDesc = - GetDefaultCodecDesc(desc.desc, FF_CODEC_AUDIO); - CodecDesc defaultVideoCodecDesc = - GetDefaultCodecDesc(desc.desc, FF_CODEC_VIDEO); + ui->advOutFFFormatDesc->setText(format.long_name); + + FFmpegCodec defaultAudioCodecDesc = + GetDefaultCodec(format, AUDIO); + FFmpegCodec defaultVideoCodecDesc = + GetDefaultCodec(format, VIDEO); SelectEncoder(ui->advOutFFAEncoder, defaultAudioCodecDesc.name, defaultAudioCodecDesc.id); SelectEncoder(ui->advOutFFVEncoder, defaultVideoCodecDesc.name, defaultVideoCodecDesc.id); } else { - ReloadCodecs(nullptr); + ui->advOutFFAEncoder->blockSignals(true); + ui->advOutFFVEncoder->blockSignals(true); + ui->advOutFFAEncoder->clear(); + ui->advOutFFVEncoder->clear(); + ui->advOutFFFormatDesc->setText(DEFAULT_CONTAINER_STR); } } @@ -4466,10 +4405,9 @@ void OBSBasicSettings::on_advOutFFAEncoder_currentIndexChanged(int idx) { const QVariant itemDataVariant = ui->advOutFFAEncoder->itemData(idx); if (!itemDataVariant.isNull()) { - CodecDesc desc = itemDataVariant.value(); + auto desc = itemDataVariant.value(); SetAdvOutputFFmpegEnablement( - FF_CODEC_AUDIO, desc.id != 0 || desc.name != nullptr, - true); + AUDIO, desc.id != 0 || desc.name != nullptr, true); } } @@ -4477,10 +4415,9 @@ void OBSBasicSettings::on_advOutFFVEncoder_currentIndexChanged(int idx) { const QVariant itemDataVariant = ui->advOutFFVEncoder->itemData(idx); if (!itemDataVariant.isNull()) { - CodecDesc desc = itemDataVariant.value(); + auto desc = itemDataVariant.value(); SetAdvOutputFFmpegEnablement( - FF_CODEC_VIDEO, desc.id != 0 || desc.name != nullptr, - true); + VIDEO, desc.id != 0 || desc.name != nullptr, true); } } @@ -4994,51 +4931,6 @@ void OBSBasicSettings::AdvOutSplitFileChanged() ui->advOutSplitFileSize->setVisible(splitFileType == 1); } -static const unordered_set builtin_codecs = { - "h264", "hevc", "av1", "prores", "aac", "opus", - "alac", "flac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}; - -static const unordered_map> codec_compat = { - // Technically our muxer supports HEVC and AV1 as well, but nothing else does - {"flv", {"h264", "aac"}}, - {"mpegts", {"h264", "hevc", "aac", "opus"}}, - {"hls", - {"h264", "hevc", "aac"}}, // Also using MPEG-TS, but no Opus support - {"mp4", - {"h264", "hevc", "av1", "aac", "opus", "alac", "flac", "pcm_s16le", - "pcm_s24le", "pcm_f32le"}}, - {"fragmented_mp4", - {"h264", "hevc", "av1", "aac", "opus", "alac", "flac", "pcm_s16le", - "pcm_s24le", "pcm_f32le"}}, - {"mov", - {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", - "pcm_f32le"}}, - {"fragmented_mov", - {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", - "pcm_f32le"}}, - // MKV supports everything - {"mkv", {}}, -}; - -static bool ContainerSupportsCodec(const string &container, const string &codec) -{ - auto iter = codec_compat.find(container); - if (iter == codec_compat.end()) - return false; - - auto codecs = iter->second; - // Assume everything is supported - if (codecs.empty()) - return true; - - // PCM in MP4 is only supported in FFmpeg > 6.0 - if ((container == "mp4" || container == "fragmented_mp4") && - !ff_supports_pcm_in_mp4() && codec.find("pcm_") != string::npos) - return false; - - return codecs.count(codec) > 0; -} - static void DisableIncompatibleCodecs(QComboBox *cbox, const QString &format, const QString &formatName, const QString &streamEncoder) @@ -5063,10 +4955,10 @@ static void DisableIncompatibleCodecs(QComboBox *cbox, const QString &format, bool is_compatible = ContainerSupportsCodec(format.toStdString(), codec); /* Fall back to FFmpeg check if codec not one of the built-in ones. */ - if (!is_compatible && !builtin_codecs.count(codec)) { + if (!is_compatible && !IsBuiltinCodec(codec)) { string ext = GetFormatExt(QT_TO_UTF8(format)); is_compatible = - ff_format_codec_compatible(codec, ext.c_str()); + FFCodecAndFormatCompatible(codec, ext.c_str()); } QStandardItemModel *model = diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index c8a926cb7..394b36f93 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -24,11 +24,10 @@ #include #include -#include - #include #include "auth-base.hpp" +#include "ffmpeg-utils.hpp" class OBSBasic; class QAbstractButton; @@ -69,20 +68,6 @@ public slots: } }; -class OBSFFDeleter { -public: - void operator()(const ff_format_desc *format) - { - ff_format_desc_free(format); - } - void operator()(const ff_codec_desc *codec) - { - ff_codec_desc_free(codec); - } -}; -using OBSFFCodecDesc = std::unique_ptr; -using OBSFFFormatDesc = std::unique_ptr; - class OBSBasicSettings : public QDialog { Q_OBJECT Q_PROPERTY(QIcon generalIcon READ GetGeneralIcon WRITE SetGeneralIcon @@ -135,7 +120,7 @@ private: static constexpr uint32_t ENCODER_HIDE_FLAGS = (OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL); - OBSFFFormatDesc formats; + std::vector formats; OBSPropertiesView *streamProperties = nullptr; OBSPropertiesView *streamEncoderProps = nullptr; @@ -239,7 +224,7 @@ private: void LoadColorSpaces(); void LoadColorFormats(); void LoadFormats(); - void ReloadCodecs(const ff_format_desc *formatDesc); + void ReloadCodecs(const FFmpegFormat &format); void UpdateColorFormatSpaceWarning(); @@ -309,7 +294,7 @@ private: void LoadAdvOutputRecordingEncoderProperties(); void LoadAdvOutputFFmpegSettings(); void LoadAdvOutputAudioSettings(); - void SetAdvOutputFFmpegEnablement(ff_codec_type encoderType, + void SetAdvOutputFFmpegEnablement(FFmpegCodecType encoderType, bool enabled, bool enableEncode = false);