UI: Migrate from libff

This commit is contained in:
derrod 2023-07-30 15:19:08 +02:00 committed by Lain
parent 182468b373
commit f0407dd1cd
7 changed files with 468 additions and 218 deletions

View File

@ -16,10 +16,6 @@ endif()
find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat) find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat)
find_package(CURL REQUIRED) 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) if(NOT TARGET OBS::json11)
add_subdirectory("${CMAKE_SOURCE_DIR}/deps/json11" "${CMAKE_BINARY_DIR}/deps/json11") add_subdirectory("${CMAKE_SOURCE_DIR}/deps/json11" "${CMAKE_BINARY_DIR}/deps/json11")
endif() endif()
@ -35,7 +31,6 @@ target_link_libraries(
FFmpeg::avformat FFmpeg::avformat
OBS::libobs OBS::libobs
OBS::frontend-api OBS::frontend-api
OBS::libff-util
OBS::json11) OBS::json11)
include(cmake/ui-qt.cmake) include(cmake/ui-qt.cmake)
@ -71,6 +66,8 @@ target_sources(
auth-oauth.cpp auth-oauth.cpp
auth-oauth.hpp auth-oauth.hpp
display-helpers.hpp display-helpers.hpp
ffmpeg-utils.cpp
ffmpeg-utils.hpp
multiview.cpp multiview.cpp
multiview.hpp multiview.hpp
obf.c obf.c

View File

@ -150,10 +150,10 @@ target_sources(
ui-validation.hpp ui-validation.hpp
multiview.cpp multiview.cpp
multiview.hpp multiview.hpp
ffmpeg-utils.cpp
ffmpeg-utils.hpp
${CMAKE_SOURCE_DIR}/deps/json11/json11.cpp ${CMAKE_SOURCE_DIR}/deps/json11/json11.cpp
${CMAKE_SOURCE_DIR}/deps/json11/json11.hpp ${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) ${CMAKE_CURRENT_BINARY_DIR}/ui-config.h)
target_sources( 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_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 target_link_libraries(obs PRIVATE CURL::libcurl FFmpeg::avcodec FFmpeg::avutil FFmpeg::avformat OBS::libobs
OBS::frontend-api) OBS::frontend-api)
@ -374,9 +374,6 @@ if(OS_WINDOWS)
if(MSVC) if(MSVC)
target_link_options(obs PRIVATE "LINKER:/IGNORE:4098" "LINKER:/IGNORE:4099") target_link_options(obs PRIVATE "LINKER:/IGNORE:4098" "LINKER:/IGNORE:4099")
target_link_libraries(obs PRIVATE OBS::w32-pthreads) 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() endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 4) if(CMAKE_SIZEOF_VOID_P EQUAL 4)

290
UI/ffmpeg-utils.cpp Normal file
View File

@ -0,0 +1,290 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
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
UI/ffmpeg-utils.hpp Normal file
View File

@ -0,0 +1,84 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
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);

View File

@ -7585,17 +7585,22 @@ void OBSBasic::AutoRemux(QString input, bool no_show)
config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2");
bool audio_is_pcm = strncmp(aCodecName, "pcm", 3) == 0; 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. */ /* FFmpeg <= 6.0 cannot remux AV1+PCM into any supported format. */
if (audio_is_pcm && !ff_supports_pcm_in_mp4() && if (audio_is_pcm && strcmp(vCodecName, "av1") == 0)
strcmp(vCodecName, "av1") == 0)
return; return;
#endif
/* Retain original container for fMP4/fMOV */ /* Retain original container for fMP4/fMOV */
if (strncmp(format, "fragmented", 10) == 0) { if (strncmp(format, "fragmented", 10) == 0) {
output += "remuxed." + suffix; output += "remuxed." + suffix;
} else if (strcmp(vCodecName, "prores") == 0 || } else if (strcmp(vCodecName, "prores") == 0) {
(audio_is_pcm && !ff_supports_pcm_in_mp4())) {
output += "mov"; output += "mov";
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 5, 100)
} else if (audio_is_pcm) {
output += "mov";
#endif
} else { } else {
output += "mp4"; output += "mp4";
} }

View File

@ -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) static inline bool ResTooHigh(uint32_t cx, uint32_t cy)
{ {
return cx > 16384 || cy > 16384; 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) 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++) { for (int i = 0; i < combo->count(); i++) {
QVariant v = combo->itemData(i); QVariant v = combo->itemData(i);
if (!v.isNull()) { if (!v.isNull()) {
if (codecDesc == v.value<CodecDesc>()) { if (codec == v.value<FFmpegCodec>()) {
return i; return i;
break;
} }
} }
} }
return -1; return -1;
} }
static CodecDesc GetDefaultCodecDesc(const ff_format_desc *formatDesc, static FFmpegCodec GetDefaultCodec(const FFmpegFormat &format,
ff_codec_type codecType) FFmpegCodecType codecType)
{ {
int id = 0; int id = 0;
switch (codecType) { switch (codecType) {
case FF_CODEC_AUDIO: case AUDIO:
id = ff_format_desc_audio(formatDesc); id = format.audio_codec;
break; break;
case FF_CODEC_VIDEO: case VIDEO:
id = ff_format_desc_video(formatDesc); id = format.video_codec;
break; break;
default: default:
return CodecDesc(); return FFmpegCodec();
} }
return CodecDesc(ff_format_desc_get_default_name(formatDesc, codecType), return FFmpegCodec{format.GetDefaultName(codecType), nullptr, id};
id);
} }
#define INVALID_BITRATE 10000 #define INVALID_BITRATE 10000
@ -754,9 +708,6 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
//Apply button disabled until change. //Apply button disabled until change.
EnableApplyButton(false); EnableApplyButton(false);
// Initialize libff library
ff_init();
installEventFilter(new SettingsEventFilter()); installEventFilter(new SettingsEventFilter());
LoadColorRanges(); LoadColorRanges();
@ -1140,25 +1091,21 @@ void OBSBasicSettings::LoadFormats()
#define FORMAT_STR(str) QTStr("Basic.Settings.Output.Format." str) #define FORMAT_STR(str) QTStr("Basic.Settings.Output.Format." str)
ui->advOutFFFormat->blockSignals(true); ui->advOutFFFormat->blockSignals(true);
formats.reset(ff_format_supported()); formats = GetSupportedFormats();
const ff_format_desc *format = formats.get();
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) { if (audio || video) {
QString itemText(ff_format_desc_name(format)); QString itemText(format.name);
if (audio ^ video) if (audio ^ video)
itemText += QString(" (%1)").arg( itemText += QString(" (%1)").arg(
audio ? AUDIO_STR : VIDEO_STR); audio ? AUDIO_STR : VIDEO_STR);
ui->advOutFFFormat->addItem( ui->advOutFFFormat->addItem(
itemText, QVariant::fromValue(formatDesc)); itemText, QVariant::fromValue(format));
} }
format = ff_format_desc_next(format);
} }
ui->advOutFFFormat->model()->sort(0); ui->advOutFFFormat->model()->sort(0);
@ -1187,26 +1134,22 @@ void OBSBasicSettings::LoadFormats()
#undef FORMAT_STR #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)); QString itemText(codec.name);
if (ff_codec_desc_is_alias(codec_desc)) if (codec.alias)
itemText += QString(" (%1)").arg( itemText += QString(" (%1)").arg(codec.base_name);
ff_codec_desc_base_name(codec_desc));
CodecDesc cd(ff_codec_desc_name(codec_desc), combo->addItem(itemText, QVariant::fromValue(codec));
ff_codec_desc_id(codec_desc));
combo->addItem(itemText, QVariant::fromValue(cd));
} }
#define AV_ENCODER_DEFAULT_STR \ #define AV_ENCODER_DEFAULT_STR \
QTStr("Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault") QTStr("Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault")
static void AddDefaultCodec(QComboBox *combo, const ff_format_desc *formatDesc, static void AddDefaultCodec(QComboBox *combo, const FFmpegFormat &format,
ff_codec_type codecType) FFmpegCodecType codecType)
{ {
CodecDesc cd = GetDefaultCodecDesc(formatDesc, codecType); FFmpegCodec cd = GetDefaultCodec(format, codecType);
int existingIdx = FindEncoder(combo, cd.name, cd.id); int existingIdx = FindEncoder(combo, cd.name, cd.id);
if (existingIdx >= 0) if (existingIdx >= 0)
@ -1219,48 +1162,41 @@ static void AddDefaultCodec(QComboBox *combo, const ff_format_desc *formatDesc,
#define AV_ENCODER_DISABLE_STR \ #define AV_ENCODER_DISABLE_STR \
QTStr("Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable") 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->advOutFFAEncoder->blockSignals(true);
ui->advOutFFVEncoder->blockSignals(true); ui->advOutFFVEncoder->blockSignals(true);
ui->advOutFFAEncoder->clear(); ui->advOutFFAEncoder->clear();
ui->advOutFFVEncoder->clear(); ui->advOutFFVEncoder->clear();
if (formatDesc == nullptr) bool ignore_compatibility = ui->advOutFFIgnoreCompat->isChecked();
return; vector<FFmpegCodec> supportedCodecs =
GetFormatCodecs(format, ignore_compatibility);
bool ignore_compatability = ui->advOutFFIgnoreCompat->isChecked(); for (auto &codec : supportedCodecs) {
OBSFFCodecDesc codecDescs( switch (codec.type) {
ff_codec_supported(formatDesc, ignore_compatability)); case AUDIO:
const ff_codec_desc *codec = codecDescs.get();
while (codec != nullptr) {
switch (ff_codec_desc_type(codec)) {
case FF_CODEC_AUDIO:
AddCodec(ui->advOutFFAEncoder, codec); AddCodec(ui->advOutFFAEncoder, codec);
break; break;
case FF_CODEC_VIDEO: case VIDEO:
AddCodec(ui->advOutFFVEncoder, codec); AddCodec(ui->advOutFFVEncoder, codec);
break; break;
default: default:
break; break;
} }
codec = ff_codec_desc_next(codec);
} }
if (ff_format_desc_has_audio(formatDesc)) if (format.HasAudio())
AddDefaultCodec(ui->advOutFFAEncoder, formatDesc, AddDefaultCodec(ui->advOutFFAEncoder, format,
FF_CODEC_AUDIO); FFmpegCodecType::AUDIO);
if (ff_format_desc_has_video(formatDesc)) if (format.HasVideo())
AddDefaultCodec(ui->advOutFFVEncoder, formatDesc, AddDefaultCodec(ui->advOutFFVEncoder, format,
FF_CODEC_VIDEO); FFmpegCodecType::VIDEO);
ui->advOutFFAEncoder->model()->sort(0); ui->advOutFFAEncoder->model()->sort(0);
ui->advOutFFVEncoder->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->advOutFFAEncoder->insertItem(0, AV_ENCODER_DISABLE_STR, disable);
ui->advOutFFVEncoder->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, static void SelectFormat(QComboBox *combo, const char *name,
const char *mimeType) const char *mimeType)
{ {
FormatDesc formatDesc(name, mimeType); FFmpegFormat format{name, mimeType};
for (int i = 0; i < combo->count(); i++) { for (int i = 0; i < combo->count(); i++) {
QVariant v = combo->itemData(i); QVariant v = combo->itemData(i);
if (!v.isNull()) { if (!v.isNull()) {
if (formatDesc == v.value<FormatDesc>()) { if (format == v.value<FFmpegFormat>()) {
combo->setCurrentIndex(i); combo->setCurrentIndex(i);
return; return;
} }
@ -2523,14 +2459,14 @@ void OBSBasicSettings::LoadOutputSettings()
loading = false; loading = false;
} }
void OBSBasicSettings::SetAdvOutputFFmpegEnablement(ff_codec_type encoderType, void OBSBasicSettings::SetAdvOutputFFmpegEnablement(FFmpegCodecType encoderType,
bool enabled, bool enabled,
bool enableEncoder) bool enableEncoder)
{ {
bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale"); bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale");
switch (encoderType) { switch (encoderType) {
case FF_CODEC_VIDEO: case FFmpegCodecType::VIDEO:
ui->advOutFFVBitrate->setEnabled(enabled); ui->advOutFFVBitrate->setEnabled(enabled);
ui->advOutFFVGOPSize->setEnabled(enabled); ui->advOutFFVGOPSize->setEnabled(enabled);
ui->advOutFFUseRescale->setEnabled(enabled); ui->advOutFFUseRescale->setEnabled(enabled);
@ -2538,7 +2474,7 @@ void OBSBasicSettings::SetAdvOutputFFmpegEnablement(ff_codec_type encoderType,
ui->advOutFFVEncoder->setEnabled(enabled || enableEncoder); ui->advOutFFVEncoder->setEnabled(enabled || enableEncoder);
ui->advOutFFVCfg->setEnabled(enabled); ui->advOutFFVCfg->setEnabled(enabled);
break; break;
case FF_CODEC_AUDIO: case FFmpegCodecType::AUDIO:
ui->advOutFFABitrate->setEnabled(enabled); ui->advOutFFABitrate->setEnabled(enabled);
ui->advOutFFAEncoder->setEnabled(enabled || enableEncoder); ui->advOutFFAEncoder->setEnabled(enabled || enableEncoder);
ui->advOutFFACfg->setEnabled(enabled); ui->advOutFFACfg->setEnabled(enabled);
@ -3766,13 +3702,13 @@ void OBSBasicSettings::SaveFormat(QComboBox *combo)
{ {
QVariant v = combo->currentData(); QVariant v = combo->currentData();
if (!v.isNull()) { if (!v.isNull()) {
FormatDesc desc = v.value<FormatDesc>(); auto format = v.value<FFmpegFormat>();
config_set_string(main->Config(), "AdvOut", "FFFormat", config_set_string(main->Config(), "AdvOut", "FFFormat",
desc.name); format.name);
config_set_string(main->Config(), "AdvOut", "FFFormatMimeType", 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 : ""; string extStr = ext ? ext : "";
char *comma = strchr(&extStr[0], ','); char *comma = strchr(&extStr[0], ',');
@ -3795,9 +3731,10 @@ void OBSBasicSettings::SaveEncoder(QComboBox *combo, const char *section,
const char *value) const char *value)
{ {
QVariant v = combo->currentData(); QVariant v = combo->currentData();
CodecDesc cd; FFmpegCodec cd{};
if (!v.isNull()) if (!v.isNull())
cd = v.value<CodecDesc>(); cd = v.value<FFmpegCodec>();
config_set_int(main->Config(), section, config_set_int(main->Config(), section,
QT_TO_UTF8(QString("%1Id").arg(value)), cd.id); QT_TO_UTF8(QString("%1Id").arg(value)), cd.id);
if (cd.id != 0) if (cd.id != 0)
@ -4437,27 +4374,29 @@ void OBSBasicSettings::on_advOutFFFormat_currentIndexChanged(int idx)
const QVariant itemDataVariant = ui->advOutFFFormat->itemData(idx); const QVariant itemDataVariant = ui->advOutFFFormat->itemData(idx);
if (!itemDataVariant.isNull()) { if (!itemDataVariant.isNull()) {
FormatDesc desc = itemDataVariant.value<FormatDesc>(); auto format = itemDataVariant.value<FFmpegFormat>();
SetAdvOutputFFmpegEnablement( SetAdvOutputFFmpegEnablement(FFmpegCodecType::AUDIO,
FF_CODEC_AUDIO, ff_format_desc_has_audio(desc.desc), format.HasAudio(), false);
false); SetAdvOutputFFmpegEnablement(FFmpegCodecType::VIDEO,
SetAdvOutputFFmpegEnablement( format.HasVideo(), false);
FF_CODEC_VIDEO, ff_format_desc_has_video(desc.desc), ReloadCodecs(format);
false);
ReloadCodecs(desc.desc);
ui->advOutFFFormatDesc->setText(
ff_format_desc_long_name(desc.desc));
CodecDesc defaultAudioCodecDesc = ui->advOutFFFormatDesc->setText(format.long_name);
GetDefaultCodecDesc(desc.desc, FF_CODEC_AUDIO);
CodecDesc defaultVideoCodecDesc = FFmpegCodec defaultAudioCodecDesc =
GetDefaultCodecDesc(desc.desc, FF_CODEC_VIDEO); GetDefaultCodec(format, AUDIO);
FFmpegCodec defaultVideoCodecDesc =
GetDefaultCodec(format, VIDEO);
SelectEncoder(ui->advOutFFAEncoder, defaultAudioCodecDesc.name, SelectEncoder(ui->advOutFFAEncoder, defaultAudioCodecDesc.name,
defaultAudioCodecDesc.id); defaultAudioCodecDesc.id);
SelectEncoder(ui->advOutFFVEncoder, defaultVideoCodecDesc.name, SelectEncoder(ui->advOutFFVEncoder, defaultVideoCodecDesc.name,
defaultVideoCodecDesc.id); defaultVideoCodecDesc.id);
} else { } else {
ReloadCodecs(nullptr); ui->advOutFFAEncoder->blockSignals(true);
ui->advOutFFVEncoder->blockSignals(true);
ui->advOutFFAEncoder->clear();
ui->advOutFFVEncoder->clear();
ui->advOutFFFormatDesc->setText(DEFAULT_CONTAINER_STR); ui->advOutFFFormatDesc->setText(DEFAULT_CONTAINER_STR);
} }
} }
@ -4466,10 +4405,9 @@ void OBSBasicSettings::on_advOutFFAEncoder_currentIndexChanged(int idx)
{ {
const QVariant itemDataVariant = ui->advOutFFAEncoder->itemData(idx); const QVariant itemDataVariant = ui->advOutFFAEncoder->itemData(idx);
if (!itemDataVariant.isNull()) { if (!itemDataVariant.isNull()) {
CodecDesc desc = itemDataVariant.value<CodecDesc>(); auto desc = itemDataVariant.value<FFmpegCodec>();
SetAdvOutputFFmpegEnablement( SetAdvOutputFFmpegEnablement(
FF_CODEC_AUDIO, desc.id != 0 || desc.name != nullptr, AUDIO, desc.id != 0 || desc.name != nullptr, true);
true);
} }
} }
@ -4477,10 +4415,9 @@ void OBSBasicSettings::on_advOutFFVEncoder_currentIndexChanged(int idx)
{ {
const QVariant itemDataVariant = ui->advOutFFVEncoder->itemData(idx); const QVariant itemDataVariant = ui->advOutFFVEncoder->itemData(idx);
if (!itemDataVariant.isNull()) { if (!itemDataVariant.isNull()) {
CodecDesc desc = itemDataVariant.value<CodecDesc>(); auto desc = itemDataVariant.value<FFmpegCodec>();
SetAdvOutputFFmpegEnablement( SetAdvOutputFFmpegEnablement(
FF_CODEC_VIDEO, desc.id != 0 || desc.name != nullptr, VIDEO, desc.id != 0 || desc.name != nullptr, true);
true);
} }
} }
@ -4994,51 +4931,6 @@ void OBSBasicSettings::AdvOutSplitFileChanged()
ui->advOutSplitFileSize->setVisible(splitFileType == 1); 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, static void DisableIncompatibleCodecs(QComboBox *cbox, const QString &format,
const QString &formatName, const QString &formatName,
const QString &streamEncoder) const QString &streamEncoder)
@ -5063,10 +4955,10 @@ static void DisableIncompatibleCodecs(QComboBox *cbox, const QString &format,
bool is_compatible = bool is_compatible =
ContainerSupportsCodec(format.toStdString(), codec); ContainerSupportsCodec(format.toStdString(), codec);
/* Fall back to FFmpeg check if codec not one of the built-in ones. */ /* 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)); string ext = GetFormatExt(QT_TO_UTF8(format));
is_compatible = is_compatible =
ff_format_codec_compatible(codec, ext.c_str()); FFCodecAndFormatCompatible(codec, ext.c_str());
} }
QStandardItemModel *model = QStandardItemModel *model =

View File

@ -24,11 +24,10 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <libff/ff-util.h>
#include <obs.hpp> #include <obs.hpp>
#include "auth-base.hpp" #include "auth-base.hpp"
#include "ffmpeg-utils.hpp"
class OBSBasic; class OBSBasic;
class QAbstractButton; 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 { class OBSBasicSettings : public QDialog {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QIcon generalIcon READ GetGeneralIcon WRITE SetGeneralIcon Q_PROPERTY(QIcon generalIcon READ GetGeneralIcon WRITE SetGeneralIcon
@ -135,7 +120,7 @@ private:
static constexpr uint32_t ENCODER_HIDE_FLAGS = static constexpr uint32_t ENCODER_HIDE_FLAGS =
(OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL); (OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL);
OBSFFFormatDesc formats; std::vector<FFmpegFormat> formats;
OBSPropertiesView *streamProperties = nullptr; OBSPropertiesView *streamProperties = nullptr;
OBSPropertiesView *streamEncoderProps = nullptr; OBSPropertiesView *streamEncoderProps = nullptr;
@ -239,7 +224,7 @@ private:
void LoadColorSpaces(); void LoadColorSpaces();
void LoadColorFormats(); void LoadColorFormats();
void LoadFormats(); void LoadFormats();
void ReloadCodecs(const ff_format_desc *formatDesc); void ReloadCodecs(const FFmpegFormat &format);
void UpdateColorFormatSpaceWarning(); void UpdateColorFormatSpaceWarning();
@ -309,7 +294,7 @@ private:
void LoadAdvOutputRecordingEncoderProperties(); void LoadAdvOutputRecordingEncoderProperties();
void LoadAdvOutputFFmpegSettings(); void LoadAdvOutputFFmpegSettings();
void LoadAdvOutputAudioSettings(); void LoadAdvOutputAudioSettings();
void SetAdvOutputFFmpegEnablement(ff_codec_type encoderType, void SetAdvOutputFFmpegEnablement(FFmpegCodecType encoderType,
bool enabled, bool enabled,
bool enableEncode = false); bool enableEncode = false);