Browse Source

UI: Add Format, Audio and Video ComboBoxes

Adds ComboBoxes for selecting format and audio/video
codecs. The ComboBoxes also show only valid codec
combinations for a particular container format.
kc5nra 10 years ago
parent
commit
b218957f38

+ 1 - 0
obs/CMakeLists.txt

@@ -189,6 +189,7 @@ endif()
 
 target_link_libraries(obs
 	libobs
+	libff
 	Qt5::Widgets
 	Qt5::Network
 	${obs_PLATFORM_LIBRARIES})

+ 10 - 2
obs/data/locale/en-US.ini

@@ -276,9 +276,17 @@ Basic.Settings.Output.Adv.Recording.UseStreamEncoder="(Use stream encoder)"
 Basic.Settings.Output.Adv.FFmpeg.SaveFilter.Common="Common recording formats"
 Basic.Settings.Output.Adv.FFmpeg.SaveFilter.All="All Files"
 Basic.Settings.Output.Adv.FFmpeg.SavePathURL="File path or URL"
-Basic.Settings.Output.Adv.FFmpeg.VEncoder="Video Encoder (blank=default)"
+Basic.Settings.Output.Adv.FFmpeg.Format="Container Format"
+Basic.Settings.Output.Adv.FFmpeg.FormatAudio="Audio"
+Basic.Settings.Output.Adv.FFmpeg.FormatVideo="Video"
+Basic.Settings.Output.Adv.FFmpeg.FormatDefault="Default Format"
+Basic.Settings.Output.Adv.FFmpeg.FormatDesc="Container Format Description"
+Basic.Settings.Output.Adv.FFmpeg.FormatDescDef="Audio/Video Codec guessed from File path or URL"
+Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault="Default Encoder"
+Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable="Disable Encoder"
+Basic.Settings.Output.Adv.FFmpeg.VEncoder="Video Encoder"
 Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings="Video Encoder Settings (if any)"
-Basic.Settings.Output.Adv.FFmpeg.AEncoder="Audio Encoder (blank=default)"
+Basic.Settings.Output.Adv.FFmpeg.AEncoder="Audio Encoder"
 Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Audio Encoder Settings (if any)"
 
 # basic mode 'video' settings

+ 79 - 55
obs/forms/OBSBasicSettings.ui

@@ -1241,13 +1241,20 @@
                       </layout>
                      </item>
                      <item row="1" column="0">
+                      <widget class="QLabel" name="label_16">
+                       <property name="text">
+                        <string>Basic.Settings.Output.Adv.FFmpeg.Format</string>
+                       </property>
+                      </widget>
+                     </item>
+                     <item row="3" column="0">
                       <widget class="QLabel" name="label_40">
                        <property name="text">
                         <string>Basic.Settings.Output.VideoBitrate</string>
                        </property>
                       </widget>
                      </item>
-                     <item row="1" column="1">
+                     <item row="3" column="1">
                       <widget class="QSpinBox" name="advOutFFVBitrate">
                        <property name="minimum">
                         <number>0</number>
@@ -1260,34 +1267,60 @@
                        </property>
                       </widget>
                      </item>
-                     <item row="3" column="0">
+                     <item row="4" column="0">
+                      <widget class="QCheckBox" name="advOutFFUseRescale">
+                       <property name="sizePolicy">
+                        <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
+                         <horstretch>0</horstretch>
+                         <verstretch>0</verstretch>
+                        </sizepolicy>
+                       </property>
+                       <property name="layoutDirection">
+                        <enum>Qt::RightToLeft</enum>
+                       </property>
+                       <property name="text">
+                        <string>Basic.Settings.Output.Adv.Rescale</string>
+                       </property>
+                      </widget>
+                     </item>
+                     <item row="4" column="1">
+                      <widget class="QComboBox" name="advOutFFRescale">
+                       <property name="enabled">
+                        <bool>false</bool>
+                       </property>
+                       <property name="editable">
+                        <bool>true</bool>
+                       </property>
+                      </widget>
+                     </item>
+                     <item row="5" column="0">
                       <widget class="QLabel" name="label_37">
                        <property name="text">
                         <string>Basic.Settings.Output.Adv.FFmpeg.VEncoder</string>
                        </property>
                       </widget>
                      </item>
-                     <item row="3" column="1">
-                      <widget class="QLineEdit" name="advOutFFVEncoder"/>
+                     <item row="5" column="1">
+                      <widget class="QComboBox" name="advOutFFVEncoder"/>
                      </item>
-                     <item row="4" column="0">
+                     <item row="6" column="0">
                       <widget class="QLabel" name="label_38">
                        <property name="text">
                         <string>Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings</string>
                        </property>
                       </widget>
                      </item>
-                     <item row="4" column="1">
+                     <item row="6" column="1">
                       <widget class="QLineEdit" name="advOutFFVCfg"/>
                      </item>
-                     <item row="5" column="0">
+                     <item row="7" column="0">
                       <widget class="QLabel" name="label_41">
                        <property name="text">
                         <string>Basic.Settings.Output.AudioBitrate</string>
                        </property>
                       </widget>
                      </item>
-                     <item row="5" column="1">
+                     <item row="7" column="1">
                       <widget class="QSpinBox" name="advOutFFABitrate">
                        <property name="minimum">
                         <number>32</number>
@@ -1303,60 +1336,14 @@
                        </property>
                       </widget>
                      </item>
-                     <item row="6" column="0">
+                     <item row="8" column="0">
                       <widget class="QLabel" name="label_47">
                        <property name="text">
                         <string>Basic.Settings.Output.Adv.AudioTrack</string>
                        </property>
                       </widget>
                      </item>
-                     <item row="7" column="0">
-                      <widget class="QLabel" name="label_39">
-                       <property name="text">
-                        <string>Basic.Settings.Output.Adv.FFmpeg.AEncoder</string>
-                       </property>
-                      </widget>
-                     </item>
-                     <item row="7" column="1">
-                      <widget class="QLineEdit" name="advOutFFAEncoder"/>
-                     </item>
-                     <item row="8" column="0">
-                      <widget class="QLabel" name="label_46">
-                       <property name="text">
-                        <string>Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings</string>
-                       </property>
-                      </widget>
-                     </item>
                      <item row="8" column="1">
-                      <widget class="QLineEdit" name="advOutFFACfg"/>
-                     </item>
-                     <item row="2" column="0">
-                      <widget class="QCheckBox" name="advOutFFUseRescale">
-                       <property name="sizePolicy">
-                        <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
-                         <horstretch>0</horstretch>
-                         <verstretch>0</verstretch>
-                        </sizepolicy>
-                       </property>
-                       <property name="layoutDirection">
-                        <enum>Qt::RightToLeft</enum>
-                       </property>
-                       <property name="text">
-                        <string>Basic.Settings.Output.Adv.Rescale</string>
-                       </property>
-                      </widget>
-                     </item>
-                     <item row="2" column="1">
-                      <widget class="QComboBox" name="advOutFFRescale">
-                       <property name="enabled">
-                        <bool>false</bool>
-                       </property>
-                       <property name="editable">
-                        <bool>true</bool>
-                       </property>
-                      </widget>
-                     </item>
-                     <item row="6" column="1">
                       <widget class="QWidget" name="widget_10" native="true">
                        <property name="sizePolicy">
                         <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
@@ -1411,6 +1398,43 @@
                        </layout>
                       </widget>
                      </item>
+                     <item row="9" column="0">
+                      <widget class="QLabel" name="label_39">
+                       <property name="text">
+                        <string>Basic.Settings.Output.Adv.FFmpeg.AEncoder</string>
+                       </property>
+                      </widget>
+                     </item>
+                     <item row="10" column="0">
+                      <widget class="QLabel" name="label_46">
+                       <property name="text">
+                        <string>Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings</string>
+                       </property>
+                      </widget>
+                     </item>
+                     <item row="10" column="1">
+                      <widget class="QLineEdit" name="advOutFFACfg"/>
+                     </item>
+                     <item row="1" column="1">
+                      <widget class="QComboBox" name="advOutFFFormat"/>
+                     </item>
+                     <item row="2" column="1">
+                      <widget class="QLabel" name="advOutFFFormatDesc">
+                       <property name="text">
+                        <string/>
+                       </property>
+                      </widget>
+                     </item>
+                     <item row="2" column="0">
+                      <widget class="QLabel" name="label_44">
+                       <property name="text">
+                        <string>Basic.Settings.Output.Adv.FFmpeg.FormatDesc</string>
+                       </property>
+                      </widget>
+                     </item>
+                     <item row="9" column="1">
+                      <widget class="QComboBox" name="advOutFFAEncoder"/>
+                     </item>
                     </layout>
                    </widget>
                   </widget>

+ 12 - 0
obs/window-basic-main-outputs.cpp

@@ -495,8 +495,14 @@ inline void AdvancedOutput::SetupFFmpeg()
 			"FFRescale");
 	const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
 			"FFRescaleRes");
+	const char *formatName = config_get_string(main->Config(), "AdvOut",
+			"FFFormat");
+	const char *mimeType = config_get_string(main->Config(), "AdvOut",
+			"FFFormatMimeType");
 	const char *vEncoder = config_get_string(main->Config(), "AdvOut",
 			"FFVEncoder");
+	int vEncoderId = config_get_int(main->Config(), "AdvOut",
+			"FFVEncoderId");
 	const char *vEncCustom = config_get_string(main->Config(), "AdvOut",
 			"FFVCustom");
 	int aBitrate = config_get_int(main->Config(), "AdvOut",
@@ -505,16 +511,22 @@ inline void AdvancedOutput::SetupFFmpeg()
 			"FFAudioTrack");
 	const char *aEncoder = config_get_string(main->Config(), "AdvOut",
 			"FFAEncoder");
+	int aEncoderId = config_get_int(main->Config(), "AdvOut",
+			"FFAEncoderId");
 	const char *aEncCustom = config_get_string(main->Config(), "AdvOut",
 			"FFACustom");
 	obs_data_t *settings = obs_data_create();
 
 	obs_data_set_string(settings, "url", url);
+	obs_data_set_string(settings, "format_name", formatName);
+	obs_data_set_string(settings, "format_mime_type", mimeType);
 	obs_data_set_int(settings, "video_bitrate", vBitrate);
 	obs_data_set_string(settings, "video_encoder", vEncoder);
+	obs_data_set_int(settings, "video_encoder_id", vEncoderId);
 	obs_data_set_string(settings, "video_settings", vEncCustom);
 	obs_data_set_int(settings, "audio_bitrate", aBitrate);
 	obs_data_set_string(settings, "audio_encoder", aEncoder);
+	obs_data_set_int(settings, "audio_encoder_id", aEncoderId);
 	obs_data_set_string(settings, "audio_settings", aEncCustom);
 
 	if (rescale && rescaleRes && *rescaleRes) {

+ 357 - 6
obs/window-basic-settings.cpp

@@ -25,6 +25,9 @@
 #include <QCloseEvent>
 #include <QFileDialog>
 #include <QDirIterator>
+#include <QVariant>
+#include <QTreeView>
+#include <QStandardItemModel>
 
 #include "obs-app.hpp"
 #include "platform.hpp"
@@ -37,6 +40,51 @@
 
 using namespace std;
 
+// Used for QVariant in codec comboboxes
+namespace {
+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 ((name == nullptr) ^ (f.name == nullptr))
+			return false;
+		if (name != nullptr && strcmp(name, f.name) != 0)
+			return false;
+		if ((mimeType == nullptr) ^ (f.mimeType == nullptr))
+			return false;
+		if (mimeType != nullptr && strcmp(mimeType, f.mimeType) != 0)
+			return false;
+		return true;
+	}
+};
+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 ((name == nullptr) ^ (codecDesc.name == nullptr))
+			return false;
+		if (id != codecDesc.id)
+			return false;
+		return name == nullptr || strcmp(name, codecDesc.name) == 0;
+	}
+};
+}
+Q_DECLARE_METATYPE(FormatDesc)
+Q_DECLARE_METATYPE(CodecDesc)
+
 /* parses "[width]x[height]", string, i.e. 1024x768 */
 static bool ConvertResText(const char *res, uint32_t &cx, uint32_t &cy)
 {
@@ -102,6 +150,40 @@ static inline QString GetComboData(QComboBox *combo)
 	return combo->itemData(idx).toString();
 }
 
+static int FindEncoder(QComboBox *combo, const char *name, int id)
+{
+	CodecDesc codecDesc(name, id);
+	for(int i = 0; i < combo->count(); i++) {
+		QVariant v = combo->itemData(i);
+		if (!v.isNull()) {
+			if (codecDesc == v.value<CodecDesc>()) {
+				return i;
+				break;
+			}
+		}
+	}
+	return -1;
+}
+
+static CodecDesc GetDefaultCodecDesc(const ff_format_desc *formatDesc,
+		ff_codec_type codecType)
+{
+	int id = 0;
+	switch (codecType) {
+	case FF_CODEC_AUDIO:
+		id = ff_format_desc_audio(formatDesc);
+		break;
+	case FF_CODEC_VIDEO:
+		id = ff_format_desc_video(formatDesc);
+		break;
+	default:
+		return CodecDesc();
+	}
+
+	return CodecDesc(ff_format_desc_get_default_name(formatDesc, codecType),
+			id);
+}
+
 void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal,
 		const char *slot)
 {
@@ -173,17 +255,18 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
 	HookWidget(ui->advOutRecTrack3,      CHECK_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutRecTrack4,      CHECK_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFURL,          EDIT_CHANGED,   OUTPUTS_CHANGED);
+	HookWidget(ui->advOutFFFormat,       COMBO_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFVBitrate,     SCROLL_CHANGED, OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFUseRescale,   CHECK_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFRescale,      CBEDIT_CHANGED, OUTPUTS_CHANGED);
-	HookWidget(ui->advOutFFVEncoder,     EDIT_CHANGED,   OUTPUTS_CHANGED);
+	HookWidget(ui->advOutFFVEncoder,     COMBO_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFVCfg,         EDIT_CHANGED,   OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFABitrate,     SCROLL_CHANGED, OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFTrack1,       CHECK_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFTrack2,       CHECK_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFTrack3,       CHECK_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFTrack4,       CHECK_CHANGED,  OUTPUTS_CHANGED);
-	HookWidget(ui->advOutFFAEncoder,     EDIT_CHANGED,   OUTPUTS_CHANGED);
+	HookWidget(ui->advOutFFAEncoder,     COMBO_CHANGED,   OUTPUTS_CHANGED);
 	HookWidget(ui->advOutFFACfg,         EDIT_CHANGED,   OUTPUTS_CHANGED);
 	HookWidget(ui->advOutTrack1Bitrate,  COMBO_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutTrack1Name,     EDIT_CHANGED,   OUTPUTS_CHANGED);
@@ -219,9 +302,13 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
 	//Apply button disabled until change.
 	EnableApplyButton(false);
 
+	// Initialize libff library
+	ff_init();
+
 	LoadServiceTypes();
 	LoadEncoderTypes();
 	LoadColorRanges();
+	LoadFormats();
 	LoadSettings(false);
 }
 
@@ -320,6 +407,122 @@ void OBSBasicSettings::LoadColorRanges()
 	ui->colorRange->addItem(CS_FULL_STR, "Full");
 }
 
+#define AV_FORMAT_DEFAULT_STR \
+	QTStr("Basic.Settings.Output.Adv.FFmpeg.FormatDefault")
+#define AUDIO_STR \
+	QTStr("Basic.Settings.Output.Adv.FFmpeg.FormatAudio")
+#define VIDEO_STR \
+	QTStr("Basic.Settings.Output.Adv.FFmpeg.FormatVideo")
+
+void OBSBasicSettings::LoadFormats()
+{
+	formats.reset(ff_format_supported());
+	const ff_format_desc *format = formats.get();
+
+	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));
+			if (audio ^ video)
+				itemText += QString(" (%1)").arg(
+						audio ? AUDIO_STR : VIDEO_STR);
+
+			ui->advOutFFFormat->addItem(itemText,
+					qVariantFromValue(formatDesc));
+		}
+
+		format = ff_format_desc_next(format);
+	}
+
+	ui->advOutFFFormat->model()->sort(0);
+
+	ui->advOutFFFormat->insertItem(0, AV_FORMAT_DEFAULT_STR);
+}
+
+static void AddCodec(QComboBox *combo, const ff_codec_desc *codec_desc)
+{
+	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));
+
+	CodecDesc cd(ff_codec_desc_name(codec_desc),
+			ff_codec_desc_id(codec_desc));
+
+	combo->addItem(itemText, qVariantFromValue(cd));
+}
+
+#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)
+{
+	CodecDesc cd = GetDefaultCodecDesc(formatDesc, codecType);
+
+	int existingIdx = FindEncoder(combo, cd.name, cd.id);
+	if (existingIdx >= 0)
+		combo->removeItem(existingIdx);
+
+	combo->addItem(QString("%1 (%2)").arg(cd.name, AV_ENCODER_DEFAULT_STR),
+			qVariantFromValue(cd));
+}
+
+#define AV_ENCODER_DISABLE_STR \
+	QTStr("Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable")
+
+void OBSBasicSettings::ReloadCodecs(const ff_format_desc *formatDesc)
+{
+	ui->advOutFFAEncoder->blockSignals(true);
+	ui->advOutFFVEncoder->blockSignals(true);
+	ui->advOutFFAEncoder->clear();
+	ui->advOutFFVEncoder->clear();
+
+	if (formatDesc == nullptr)
+		return;
+
+	OBSFFCodecDesc codecDescs(ff_codec_supported(formatDesc));
+
+	const ff_codec_desc *codec = codecDescs.get();
+
+	while(codec != nullptr) {
+		switch (ff_codec_desc_type(codec)) {
+		case FF_CODEC_AUDIO:
+			AddCodec(ui->advOutFFAEncoder, codec);
+			break;
+		case FF_CODEC_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);
+
+	ui->advOutFFAEncoder->model()->sort(0);
+	ui->advOutFFVEncoder->model()->sort(0);
+
+	QVariant disable = qVariantFromValue(CodecDesc());
+
+	ui->advOutFFAEncoder->insertItem(0, AV_ENCODER_DISABLE_STR, disable);
+	ui->advOutFFVEncoder->insertItem(0, AV_ENCODER_DISABLE_STR, disable);
+
+	ui->advOutFFAEncoder->blockSignals(false);
+	ui->advOutFFVEncoder->blockSignals(false);
+}
+
 void OBSBasicSettings::LoadLanguageList()
 {
 	const char *currentLang = App()->GetLocale();
@@ -783,9 +986,38 @@ void OBSBasicSettings::LoadAdvOutputRecordingEncoderProperties()
 	SetComboByValue(ui->advOutRecEncoder, encoder);
 }
 
+static void SelectFormat(QComboBox *combo, const char *name,
+		const char *mimeType)
+{
+	FormatDesc formatDesc(name, mimeType);
+
+	for(int i = 0; i < combo->count(); i++) {
+		QVariant v = combo->itemData(i);
+		if (!v.isNull()) {
+			if (formatDesc == v.value<FormatDesc>()) {
+				combo->setCurrentIndex(i);
+				return;
+			}
+		}
+	}
+
+	combo->setCurrentIndex(0);
+}
+
+static void SelectEncoder(QComboBox *combo, const char *name, int id)
+{
+	int idx = FindEncoder(combo, name, id);
+	if (idx >= 0)
+		combo->setCurrentIndex(idx);
+}
+
 void OBSBasicSettings::LoadAdvOutputFFmpegSettings()
 {
 	const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");
+	const char *format = config_get_string(main->Config(), "AdvOut",
+			"FFFormat");
+	const char *mimeType = config_get_string(main->Config(), "AdvOut",
+			"FFFormatMimeType");
 	int videoBitrate = config_get_int(main->Config(), "AdvOut",
 			"FFVBitrate");
 	bool rescale = config_get_bool(main->Config(), "AdvOut",
@@ -794,6 +1026,8 @@ void OBSBasicSettings::LoadAdvOutputFFmpegSettings()
 			"FFRescaleRes");
 	const char *vEncoder = config_get_string(main->Config(), "AdvOut",
 			"FFVEncoder");
+	int vEncoderId = config_get_int(main->Config(), "AdvOut",
+			"FFVEncoderId");
 	const char *vEncCustom = config_get_string(main->Config(), "AdvOut",
 			"FFVCustom");
 	int audioBitrate = config_get_int(main->Config(), "AdvOut",
@@ -802,18 +1036,21 @@ void OBSBasicSettings::LoadAdvOutputFFmpegSettings()
 			"FFAudioTrack");
 	const char *aEncoder = config_get_string(main->Config(), "AdvOut",
 			"FFAEncoder");
+	int aEncoderId = config_get_int(main->Config(), "AdvOut",
+			"FFAEncoderId");
 	const char *aEncCustom = config_get_string(main->Config(), "AdvOut",
 			"FFACustom");
 
 	ui->advOutFFURL->setText(url);
+	SelectFormat(ui->advOutFFFormat, format, mimeType);
 	ui->advOutFFVBitrate->setValue(videoBitrate);
 	ui->advOutFFUseRescale->setChecked(rescale);
 	ui->advOutFFRescale->setEnabled(rescale);
 	ui->advOutFFRescale->setCurrentText(rescaleRes);
-	ui->advOutFFVEncoder->setText(vEncoder);
+	SelectEncoder(ui->advOutFFVEncoder, vEncoder, vEncoderId);
 	ui->advOutFFVCfg->setText(vEncCustom);
 	ui->advOutFFABitrate->setValue(audioBitrate);
-	ui->advOutFFAEncoder->setText(aEncoder);
+	SelectEncoder(ui->advOutFFAEncoder, aEncoder, aEncoderId);
 	ui->advOutFFACfg->setText(aEncCustom);
 
 	switch (audioTrack) {
@@ -887,6 +1124,34 @@ void OBSBasicSettings::LoadOutputSettings()
 	loading = false;
 }
 
+void OBSBasicSettings::SetAdvOutputFFmpegEnablement(
+		ff_codec_type encoderType, bool enabled,
+		bool enableEncoder)
+{
+	bool rescale = config_get_bool(main->Config(), "AdvOut",
+			"FFRescale");
+
+	switch (encoderType) {
+	case FF_CODEC_VIDEO:
+		ui->advOutFFVBitrate->setEnabled(enabled);
+		ui->advOutFFUseRescale->setEnabled(enabled);
+		ui->advOutFFRescale->setEnabled(enabled && rescale);
+		ui->advOutFFVEncoder->setEnabled(enabled || enableEncoder);
+		ui->advOutFFVCfg->setEnabled(enabled);
+		break;
+	case FF_CODEC_AUDIO:
+		ui->advOutFFABitrate->setEnabled(enabled);
+		ui->advOutFFAEncoder->setEnabled(enabled || enableEncoder);
+		ui->advOutFFACfg->setEnabled(enabled);
+		ui->advOutFFTrack1->setEnabled(enabled);
+		ui->advOutFFTrack2->setEnabled(enabled);
+		ui->advOutFFTrack3->setEnabled(enabled);
+		ui->advOutFFTrack4->setEnabled(enabled);
+	default:
+		break;
+	}
+}
+
 static inline void LoadListValue(QComboBox *widget, const char *text,
 		const char *val)
 {
@@ -1147,6 +1412,38 @@ static void SaveTrackIndex(config_t *config, const char *section,
 	else if (check4->isChecked()) config_set_int(config, section, name, 4);
 }
 
+void OBSBasicSettings::SaveFormat(QComboBox *combo)
+{
+	QVariant v = combo->currentData();
+	if (!v.isNull()) {
+		FormatDesc desc = v.value<FormatDesc>();
+		config_set_string(main->Config(), "AdvOut", "FFFormat",
+				desc.name);
+		config_set_string(main->Config(), "AdvOut", "FFFormatMimeType",
+				desc.mimeType);
+	} else {
+		config_set_string(main->Config(), "AdvOut", "FFFormat",
+				nullptr);
+		config_set_string(main->Config(), "AdvOut", "FFFormatMimeType",
+				nullptr);
+	}
+}
+
+void OBSBasicSettings::SaveEncoder(QComboBox *combo, const char *section,
+		const char *value)
+{
+	QVariant v = combo->currentData();
+	CodecDesc cd;
+	if (!v.isNull())
+		cd = v.value<CodecDesc>();
+	config_set_int(main->Config(), section,
+			QT_TO_UTF8(QString("%1Id").arg(value)), cd.id);
+	if (cd.id != 0)
+		config_set_string(main->Config(), section, value, cd.name);
+	else
+		config_set_string(main->Config(), section, value, nullptr);
+}
+
 void OBSBasicSettings::SaveOutputSettings()
 {
 	config_set_string(main->Config(), "Output", "Mode",
@@ -1190,13 +1487,14 @@ void OBSBasicSettings::SaveOutputSettings()
 			ui->advOutRecTrack3, ui->advOutRecTrack4);
 
 	SaveEdit(ui->advOutFFURL, "AdvOut", "FFURL");
+	SaveFormat(ui->advOutFFFormat);
 	SaveSpinBox(ui->advOutFFVBitrate, "AdvOut", "FFVBitrate");
 	SaveCheckBox(ui->advOutFFUseRescale, "AdvOut", "FFRescale");
 	SaveCombo(ui->advOutFFRescale, "AdvOut", "FFRescaleRes");
-	SaveEdit(ui->advOutFFVEncoder, "AdvOut", "FFVEncoder");
+	SaveEncoder(ui->advOutFFVEncoder, "AdvOut", "FFVEncoder");
 	SaveEdit(ui->advOutFFVCfg, "AdvOut", "FFVCustom");
 	SaveSpinBox(ui->advOutFFABitrate, "AdvOut", "FFABitrate");
-	SaveEdit(ui->advOutFFAEncoder, "AdvOut", "FFAEncoder");
+	SaveEncoder(ui->advOutFFAEncoder, "AdvOut", "FFAEncoder");
 	SaveEdit(ui->advOutFFACfg, "AdvOut", "FFACustom");
 	SaveTrackIndex(main->Config(), "AdvOut", "FFAudioTrack",
 			ui->advOutFFTrack1, ui->advOutFFTrack2,
@@ -1442,6 +1740,59 @@ void OBSBasicSettings::on_advOutRecEncoder_currentIndexChanged(int idx)
 	}
 }
 
+#define DEFAULT_CONTAINER_STR \
+	QTStr("Basic.Settings.Output.Adv.FFmpeg.FormatDescDef")
+
+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);
+		SelectEncoder(ui->advOutFFAEncoder, defaultAudioCodecDesc.name,
+				defaultAudioCodecDesc.id);
+		SelectEncoder(ui->advOutFFVEncoder, defaultVideoCodecDesc.name,
+				defaultVideoCodecDesc.id);
+	} else {
+		ReloadCodecs(nullptr);
+		ui->advOutFFFormatDesc->setText(DEFAULT_CONTAINER_STR);
+	}
+}
+
+void OBSBasicSettings::on_advOutFFAEncoder_currentIndexChanged(int idx)
+{
+	const QVariant itemDataVariant = ui->advOutFFAEncoder->itemData(idx);
+	if (!itemDataVariant.isNull()) {
+		CodecDesc desc = itemDataVariant.value<CodecDesc>();
+		SetAdvOutputFFmpegEnablement(FF_CODEC_AUDIO,
+				desc.id != 0 || desc.name != nullptr, true);
+	}
+}
+
+void OBSBasicSettings::on_advOutFFVEncoder_currentIndexChanged(int idx)
+{
+	const QVariant itemDataVariant = ui->advOutFFVEncoder->itemData(idx);
+	if (!itemDataVariant.isNull()) {
+		CodecDesc desc = itemDataVariant.value<CodecDesc>();
+		SetAdvOutputFFmpegEnablement(FF_CODEC_VIDEO,
+				desc.id != 0 || desc.name != nullptr, true);
+	}
+}
+
 #define INVALID_RES_STR "Basic.Settings.Video.InvalidResolution"
 
 static bool ValidResolutions(Ui::OBSBasicSettings *ui)

+ 32 - 0
obs/window-basic-settings.hpp

@@ -23,6 +23,8 @@
 #include <memory>
 #include <string>
 
+#include <libff/ff-util.h>
+
 #include <obs.h>
 
 class OBSBasic;
@@ -32,6 +34,23 @@ class OBSPropertiesView;
 
 #include "ui_OBSBasicSettings.h"
 
+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
 
@@ -49,6 +68,8 @@ private:
 	bool loading = true;
 	std::string savedTheme;
 
+	OBSFFFormatDesc formats;
+
 	OBSPropertiesView *streamProperties = nullptr;
 	OBSPropertiesView *streamEncoderProps = nullptr;
 	OBSPropertiesView *recordEncoderProps = nullptr;
@@ -63,6 +84,9 @@ private:
 			const char *value);
 	void SaveSpinBox(QSpinBox *widget, const char *section,
 			const char *value);
+	void SaveFormat(QComboBox *combo);
+	void SaveEncoder(QComboBox *combo, const char *section,
+			const char *value);
 
 	inline bool Changed() const
 	{
@@ -93,6 +117,8 @@ private:
 	void LoadServiceTypes();
 	void LoadEncoderTypes();
 	void LoadColorRanges();
+	void LoadFormats();
+	void ReloadCodecs(const ff_format_desc *formatDesc);
 
 	void LoadGeneralSettings();
 	void LoadStream1Settings();
@@ -117,6 +143,9 @@ private:
 	void LoadAdvOutputRecordingEncoderProperties();
 	void LoadAdvOutputFFmpegSettings();
 	void LoadAdvOutputAudioSettings();
+	void SetAdvOutputFFmpegEnablement(
+		ff_codec_type encoderType, bool enabled,
+		bool enableEncode = false);
 
 	/* audio */
 	void LoadListValues(QComboBox *widget, obs_property_t *prop,
@@ -154,6 +183,9 @@ private slots:
 	void on_advOutFFPathBrowse_clicked();
 	void on_advOutEncoder_currentIndexChanged(int idx);
 	void on_advOutRecEncoder_currentIndexChanged(int idx);
+	void on_advOutFFFormat_currentIndexChanged(int idx);
+	void on_advOutFFAEncoder_currentIndexChanged(int idx);
+	void on_advOutFFVEncoder_currentIndexChanged(int idx);
 
 	void on_baseResolution_editTextChanged(const QString &text);