Browse Source

UI: Migrate from libff

derrod 2 years ago
parent
commit
f0407dd1cd

+ 2 - 5
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

+ 3 - 6
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)

+ 290 - 0
UI/ffmpeg-utils.cpp

@@ -0,0 +1,290 @@
+/******************************************************************************
+    Copyright (C) 2023 by Dennis Sädtler <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#include "ffmpeg-utils.hpp"
+
+#include <unordered_map>
+#include <unordered_set>
+
+extern "C" {
+#include <libavformat/avformat.h>
+}
+
+using namespace std;
+
+static void GetCodecsForId(const FFmpegFormat &format,
+			   vector<FFmpegCodec> &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<const AVCodecDescriptor *> GetCodecDescriptors()
+{
+	std::vector<const AVCodecDescriptor *> codecs;
+
+	const AVCodecDescriptor *desc = nullptr;
+	while ((desc = avcodec_descriptor_next(desc)) != nullptr)
+		codecs.push_back(desc);
+
+	return codecs;
+}
+
+vector<FFmpegCodec> GetFormatCodecs(const FFmpegFormat &format,
+				    bool ignore_compatibility)
+{
+	vector<FFmpegCodec> 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<FFmpegFormat> GetSupportedFormats()
+{
+	vector<FFmpegFormat> 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<string> 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<string, unordered_set<string>> 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;
+}

+ 84 - 0
UI/ffmpeg-utils.hpp

@@ -0,0 +1,84 @@
+/******************************************************************************
+    Copyright (C) 2023 by Dennis Sädtler <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#pragma once
+
+#include <qmetatype.h>
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavdevice/avdevice.h>
+}
+
+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<FFmpegFormat> GetSupportedFormats();
+std::vector<FFmpegCodec> 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);

+ 9 - 4
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";
 	}

+ 77 - 185
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<CodecDesc>()) {
+			if (codec == v.value<FFmpegCodec>()) {
 				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_compatability = ui->advOutFFIgnoreCompat->isChecked();
-	OBSFFCodecDesc codecDescs(
-		ff_codec_supported(formatDesc, ignore_compatability));
+	bool ignore_compatibility = ui->advOutFFIgnoreCompat->isChecked();
+	vector<FFmpegCodec> supportedCodecs =
+		GetFormatCodecs(format, ignore_compatibility);
 
-	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<FormatDesc>()) {
+			if (format == v.value<FFmpegFormat>()) {
 				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<FormatDesc>();
+		auto format = v.value<FFmpegFormat>();
 		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<CodecDesc>();
+		cd = v.value<FFmpegCodec>();
+
 	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<FormatDesc>();
-		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));
-
-		CodecDesc defaultAudioCodecDesc =
-			GetDefaultCodecDesc(desc.desc, FF_CODEC_AUDIO);
-		CodecDesc defaultVideoCodecDesc =
-			GetDefaultCodecDesc(desc.desc, FF_CODEC_VIDEO);
+		auto format = itemDataVariant.value<FFmpegFormat>();
+		SetAdvOutputFFmpegEnablement(FFmpegCodecType::AUDIO,
+					     format.HasAudio(), false);
+		SetAdvOutputFFmpegEnablement(FFmpegCodecType::VIDEO,
+					     format.HasVideo(), false);
+		ReloadCodecs(format);
+
+		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<CodecDesc>();
+		auto desc = itemDataVariant.value<FFmpegCodec>();
 		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<CodecDesc>();
+		auto desc = itemDataVariant.value<FFmpegCodec>();
 		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<string> builtin_codecs = {
-	"h264", "hevc", "av1",       "prores",    "aac",      "opus",
-	"alac", "flac", "pcm_s16le", "pcm_s24le", "pcm_f32le"};
-
-static const unordered_map<string, unordered_set<string>> 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 =

+ 4 - 19
UI/window-basic-settings.hpp

@@ -24,11 +24,10 @@
 #include <memory>
 #include <string>
 
-#include <libff/ff-util.h>
-
 #include <obs.hpp>
 
 #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<const ff_codec_desc, OBSFFDeleter>;
-using OBSFFFormatDesc = std::unique_ptr<const ff_format_desc, OBSFFDeleter>;
-
 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<FFmpegFormat> 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);