obs-StreamFX/source/ffmpeg/tools.cpp
Michael Fabian 'Xaymar' Dirks 5a3954ae0e project: Fix License, License headers and Copyright information
Fixes several files incorrectly stated a different license from the actual project, as well as the copyright headers included in all files. This change has no effect on the licensing terms, it should clear up a bit of confusion by contributors. Plus the files get a bit smaller, and we have less duplicated information across the entire project.

Overall the project is GPLv2 if not built with Qt, and GPLv3 if it is built with Qt. There are no parts licensed under a different license, all have been adapted from other compatible licenses into GPLv2 or GPLv3.
2023-04-05 18:59:08 +02:00

473 lines
14 KiB
C++

// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// Copyright (C) 2022 lainon <GermanAizek@yandex.ru>
// AUTOGENERATED COPYRIGHT HEADER END
#include "tools.hpp"
#include "plugin.hpp"
#include "warning-disable.hpp"
#include <list>
#include <sstream>
#include "warning-enable.hpp"
extern "C" {
#include "warning-disable.hpp"
#include <libavcodec/avcodec.h>
#include <libavutil/error.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
#include "warning-enable.hpp"
}
using namespace streamfx::ffmpeg;
const char* tools::get_pixel_format_name(AVPixelFormat v)
{
return av_get_pix_fmt_name(v);
}
const char* tools::get_color_space_name(AVColorSpace v)
{
switch (v) {
case AVCOL_SPC_RGB:
return "RGB";
case AVCOL_SPC_BT709:
return "BT.709";
case AVCOL_SPC_FCC:
return "FCC Title 47 CoFR 73.682 (a)(20)";
case AVCOL_SPC_BT470BG:
return "BT.601 625";
case AVCOL_SPC_SMPTE170M:
case AVCOL_SPC_SMPTE240M:
return "BT.601 525";
case AVCOL_SPC_YCGCO:
return "ITU-T SG16";
case AVCOL_SPC_BT2020_NCL:
return "BT.2020 NCL";
case AVCOL_SPC_BT2020_CL:
return "BT.2020 CL";
case AVCOL_SPC_SMPTE2085:
return "SMPTE 2085";
case AVCOL_SPC_CHROMA_DERIVED_NCL:
return "Chroma NCL";
case AVCOL_SPC_CHROMA_DERIVED_CL:
return "Chroma CL";
case AVCOL_SPC_ICTCP:
return "BT.2100";
case AVCOL_SPC_NB:
return "Not Part of ABI";
default:
return "Unknown";
}
}
const char* tools::get_error_description(int error)
{
thread_local char error_buf[AV_ERROR_MAX_STRING_SIZE + 1];
if (av_strerror(error, error_buf, AV_ERROR_MAX_STRING_SIZE) < 0) {
snprintf(error_buf, AV_ERROR_MAX_STRING_SIZE, "Unknown Error (%i)", error);
}
return error_buf;
}
static std::map<video_format, AVPixelFormat> const obs_to_av_format_map = {
{VIDEO_FORMAT_I420, AV_PIX_FMT_YUV420P}, // 4:2:0 YUV, 8bit, Planar
{VIDEO_FORMAT_NV12, AV_PIX_FMT_NV12}, // 4:2:0 YUV, 8bit, Packed (Y+UV)
{VIDEO_FORMAT_YVYU, AV_PIX_FMT_YVYU422}, // 4:2:0 YUV, 8bit, Packed (Y+UV)
{VIDEO_FORMAT_YUY2, AV_PIX_FMT_YUYV422}, // 4:2:2 YUV, 8bit, Packed (Y+UV)
{VIDEO_FORMAT_UYVY, AV_PIX_FMT_UYVY422}, // 4:2:2 YUV, 8bit, Packed (Y+UV)
{VIDEO_FORMAT_RGBA, AV_PIX_FMT_RGBA}, // 4:4:4:4 RGBA, 8bit, Planar
{VIDEO_FORMAT_BGRA, AV_PIX_FMT_BGRA}, // 4:4:4:4 BGRA, 8bit, Planar
{VIDEO_FORMAT_BGRX, AV_PIX_FMT_BGR0}, // 4:4:4 BGR, 8bit, Planar
{VIDEO_FORMAT_Y800, AV_PIX_FMT_GRAY8}, // 4:0:0 Y, 8bit, Planar
{VIDEO_FORMAT_I444, AV_PIX_FMT_YUV444P}, // 4:4:4 YUV, 8bit, Planar
{VIDEO_FORMAT_BGR3, AV_PIX_FMT_BGR24}, // 4:4:4 BGR, 8bit, Planar
{VIDEO_FORMAT_I422, AV_PIX_FMT_YUV422P}, // 4:2:2 YUV, 8bit, Planar
{VIDEO_FORMAT_I40A, AV_PIX_FMT_YUVA420P}, // 4:2:0:4 YUVA, 8bit, Planar
{VIDEO_FORMAT_I42A, AV_PIX_FMT_YUVA422P}, // 4:2:2:4 YUVA, 8bit, Planar
{VIDEO_FORMAT_YUVA, AV_PIX_FMT_YUVA444P}, // 4:4:4:4 YUVA, 8bit, Planar
{VIDEO_FORMAT_AYUV, AV_PIX_FMT_NONE}, // No compatible format known
{VIDEO_FORMAT_I010, AV_PIX_FMT_YUV420P10}, // 4:2:0, 10bit, Planar
{VIDEO_FORMAT_P010, AV_PIX_FMT_P010}, // 4:2:0, 10bit, Packed (Y+UV)
{VIDEO_FORMAT_I210, AV_PIX_FMT_YUV422P10}, // 4:2:2 YUV, 10bit, Planar
{VIDEO_FORMAT_I412, AV_PIX_FMT_YUV444P12}, // 4:4:4 YUV, 12bit, Planar
{VIDEO_FORMAT_YA2L, AV_PIX_FMT_YUVA444P12}, // 4:4:4:4 YUVA, 12bit, Planar
};
AVPixelFormat tools::obs_videoformat_to_avpixelformat(video_format v)
{
auto found = obs_to_av_format_map.find(v);
if (found != obs_to_av_format_map.end()) {
return found->second;
}
return AV_PIX_FMT_NONE;
}
video_format tools::avpixelformat_to_obs_videoformat(AVPixelFormat v)
{
for (const auto& kv : obs_to_av_format_map) {
if (kv.second == v)
return kv.first;
}
return VIDEO_FORMAT_NONE;
}
AVPixelFormat tools::get_least_lossy_format(const AVPixelFormat* haystack, AVPixelFormat needle)
{
int data_loss = 0;
return avcodec_find_best_pix_fmt_of_list(haystack, needle, 0, &data_loss);
}
AVColorRange tools::obs_to_av_color_range(video_range_type v)
{
switch (v) {
case VIDEO_RANGE_DEFAULT:
case VIDEO_RANGE_PARTIAL:
return AVCOL_RANGE_MPEG;
case VIDEO_RANGE_FULL:
return AVCOL_RANGE_JPEG;
}
throw std::invalid_argument("Unknown Color Range");
}
AVColorSpace tools::obs_to_av_color_space(video_colorspace v)
{
switch (v) {
case VIDEO_CS_601: // BT.601
return AVCOL_SPC_SMPTE170M;
case VIDEO_CS_DEFAULT:
case VIDEO_CS_709: // BT.709
case VIDEO_CS_SRGB: // sRGB
return AVCOL_SPC_BT709;
case VIDEO_CS_2100_PQ:
case VIDEO_CS_2100_HLG:
return AVCOL_SPC_ICTCP;
default:
throw std::invalid_argument("Unknown Color Space");
}
}
AVColorPrimaries streamfx::ffmpeg::tools::obs_to_av_color_primary(video_colorspace v)
{
switch (v) {
case VIDEO_CS_601: // BT.601
return AVCOL_PRI_SMPTE170M;
case VIDEO_CS_DEFAULT:
case VIDEO_CS_709: // BT.709
case VIDEO_CS_SRGB: // sRGB
return AVCOL_PRI_BT709;
case VIDEO_CS_2100_PQ:
case VIDEO_CS_2100_HLG:
return AVCOL_PRI_BT2020;
default:
throw std::invalid_argument("Unknown Color Primaries");
}
}
AVColorTransferCharacteristic streamfx::ffmpeg::tools::obs_to_av_color_transfer_characteristics(video_colorspace v)
{
switch (v) {
case VIDEO_CS_601: // BT.601
return AVCOL_TRC_SMPTE170M;
case VIDEO_CS_DEFAULT:
case VIDEO_CS_709: // BT.709
return AVCOL_TRC_BT709;
case VIDEO_CS_SRGB: // sRGB with IEC 61966-2-1
return AVCOL_TRC_IEC61966_2_1;
case VIDEO_CS_2100_PQ:
return AVCOL_TRC_SMPTE2084;
case VIDEO_CS_2100_HLG:
return AVCOL_TRC_ARIB_STD_B67;
default:
throw std::invalid_argument("Unknown Color Transfer Characteristics");
}
}
const char* tools::avoption_name_from_unit_value(const void* obj, std::string_view unit, int64_t value)
{
for (const AVOption* opt = nullptr; (opt = av_opt_next(obj, opt)) != nullptr;) {
// Skip all irrelevant options.
if (!opt->unit)
continue;
if (opt->unit != unit)
continue;
if (opt->name == unit)
continue;
if (opt->default_val.i64 == value)
return opt->name;
}
return nullptr;
}
bool tools::avoption_exists(const void* obj, std::string_view name)
{
for (const AVOption* opt = nullptr; (opt = av_opt_next(obj, opt)) != nullptr;) {
if (name == opt->name)
return true;
}
return false;
}
void tools::avoption_list_add_entries(const void* obj, std::string_view unit,
std::function<void(const AVOption*)> inserter)
{
for (const AVOption* opt = nullptr; (opt = av_opt_next(obj, opt)) != nullptr;) {
// Skip all irrelevant options.
if (!opt->unit)
continue;
if (opt->unit != unit)
continue;
if (opt->name == unit)
continue;
// Skip any deprecated options.
if (opt->flags & AV_OPT_FLAG_DEPRECATED)
continue;
if (inserter) {
inserter(opt);
} else {
break;
}
}
}
bool tools::can_hardware_encode(const AVCodec* codec)
{
AVPixelFormat hardware_formats[] = {AV_PIX_FMT_D3D11};
for (const AVPixelFormat* fmt = codec->pix_fmts; (fmt != nullptr) && (*fmt != AV_PIX_FMT_NONE); fmt++) {
for (auto cmp : hardware_formats) {
if (*fmt == cmp) {
return true;
}
}
}
return false;
}
std::vector<AVPixelFormat> tools::get_software_formats(const AVPixelFormat* list)
{
constexpr AVPixelFormat hardware_formats[] = {
#if FF_API_VAAPI
AV_PIX_FMT_VAAPI_MOCO,
AV_PIX_FMT_VAAPI_IDCT,
#endif
AV_PIX_FMT_VAAPI,
AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_VDPAU,
AV_PIX_FMT_QSV,
AV_PIX_FMT_MMAL,
AV_PIX_FMT_D3D11VA_VLD,
AV_PIX_FMT_CUDA,
AV_PIX_FMT_XVMC,
AV_PIX_FMT_VIDEOTOOLBOX,
AV_PIX_FMT_MEDIACODEC,
AV_PIX_FMT_D3D11,
};
std::vector<AVPixelFormat> fmts;
for (auto fmt = list; fmt && (*fmt != AV_PIX_FMT_NONE); fmt++) {
bool is_blacklisted = false;
for (auto blacklisted : hardware_formats) {
if (*fmt == blacklisted)
is_blacklisted = true;
}
if (!is_blacklisted)
fmts.push_back(*fmt);
}
fmts.push_back(AV_PIX_FMT_NONE);
return fmts;
}
void tools::context_setup_from_obs(const video_output_info* voi, AVCodecContext* context)
{
// Resolution
context->width = static_cast<int>(voi->width);
context->height = static_cast<int>(voi->height);
// Framerate
context->ticks_per_frame = 1;
context->framerate.num = context->time_base.den = static_cast<int>(voi->fps_num);
context->framerate.den = context->time_base.num = static_cast<int>(voi->fps_den);
// Aspect Ratio, Progressive
context->sample_aspect_ratio.num = 1;
context->sample_aspect_ratio.den = 1;
context->field_order = AV_FIELD_PROGRESSIVE;
// Decipher Pixel information
context->pix_fmt = obs_videoformat_to_avpixelformat(voi->format);
context->color_range = obs_to_av_color_range(voi->range);
context->colorspace = obs_to_av_color_space(voi->colorspace);
context->color_primaries = obs_to_av_color_primary(voi->colorspace);
context->color_trc = obs_to_av_color_transfer_characteristics(voi->colorspace);
// Chroma Location
switch (context->pix_fmt) {
case AV_PIX_FMT_NV12:
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVA420P:
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUVA422P:
case AV_PIX_FMT_YVYU422:
case AV_PIX_FMT_YUYV422:
case AV_PIX_FMT_UYVY422:
// libOBS merges Chroma at "Top", see H.264 specification.
context->chroma_sample_location = AVCHROMA_LOC_TOP;
break;
default:
// All other cases are unspecified.
context->chroma_sample_location = AVCHROMA_LOC_UNSPECIFIED;
break;
}
}
const char* tools::get_std_compliance_name(int compliance)
{
switch (compliance) {
case FF_COMPLIANCE_VERY_STRICT:
return "Very Strict";
case FF_COMPLIANCE_STRICT:
return "Strict";
case FF_COMPLIANCE_NORMAL:
return "Normal";
case FF_COMPLIANCE_UNOFFICIAL:
return "Unofficial";
case FF_COMPLIANCE_EXPERIMENTAL:
return "Experimental";
}
return "Invalid";
}
const char* tools::get_thread_type_name(int thread_type)
{
switch (thread_type) {
case FF_THREAD_FRAME | FF_THREAD_SLICE:
return "Slice & Frame";
case FF_THREAD_FRAME:
return "Frame";
case FF_THREAD_SLICE:
return "Slice";
default:
return "None";
}
}
void tools::print_av_option_bool(AVCodecContext* ctx_codec, const char* option, std::string_view text, bool inverse)
{
print_av_option_bool(ctx_codec, ctx_codec, option, text, inverse);
}
void tools::print_av_option_bool(AVCodecContext* ctx_codec, void* ctx_option, const char* option, std::string_view text,
bool inverse)
{
int64_t v = 0;
if (int err = av_opt_get_int(ctx_option, option, AV_OPT_SEARCH_CHILDREN, &v); err != 0) {
DLOG_INFO("[%s] %s: <Error: %s>", ctx_codec->codec->name, text.data(),
streamfx::ffmpeg::tools::get_error_description(err));
} else {
DLOG_INFO("[%s] %s: %s%s", ctx_codec->codec->name, text.data(),
(inverse ? v != 0 : v == 0) ? "Disabled" : "Enabled",
av_opt_is_set_to_default_by_name(ctx_option, option, AV_OPT_SEARCH_CHILDREN) > 0 ? " <Default>" : "");
}
}
void tools::print_av_option_int(AVCodecContext* ctx_codec, const char* option, std::string_view text,
std::string_view suffix)
{
print_av_option_int(ctx_codec, ctx_codec, option, text, suffix);
}
void tools::print_av_option_int(AVCodecContext* ctx_codec, void* ctx_option, const char* option, std::string_view text,
std::string_view suffix)
{
int64_t v = 0;
bool is_default = av_opt_is_set_to_default_by_name(ctx_option, option, AV_OPT_SEARCH_CHILDREN) > 0;
if (int err = av_opt_get_int(ctx_option, option, AV_OPT_SEARCH_CHILDREN, &v); err != 0) {
if (is_default) {
DLOG_INFO("[%s] %s: <Default>", ctx_codec->codec->name, text.data());
} else {
DLOG_INFO("[%s] %s: <Error: %s>", ctx_codec->codec->name, text.data(),
streamfx::ffmpeg::tools::get_error_description(err));
}
} else {
DLOG_INFO("[%s] %s: %" PRId64 " %s%s", ctx_codec->codec->name, text.data(), v, suffix.data(),
is_default ? " <Default>" : "");
}
}
void tools::print_av_option_string(AVCodecContext* ctx_codec, const char* option, std::string_view text,
std::function<std::string(int64_t)> decoder)
{
print_av_option_string(ctx_codec, ctx_codec, option, text, decoder);
}
void tools::print_av_option_string(AVCodecContext* ctx_codec, void* ctx_option, const char* option,
std::string_view text, std::function<std::string(int64_t)> decoder)
{
int64_t v = 0;
if (int err = av_opt_get_int(ctx_option, option, AV_OPT_SEARCH_CHILDREN, &v); err != 0) {
DLOG_INFO("[%s] %s: <Error: %s>", ctx_codec->codec->name, text.data(),
streamfx::ffmpeg::tools::get_error_description(err));
} else {
std::string name = "<Unknown>";
if (decoder)
name = decoder(v);
DLOG_INFO("[%s] %s: %s%s", ctx_codec->codec->name, text.data(), name.c_str(),
av_opt_is_set_to_default_by_name(ctx_option, option, AV_OPT_SEARCH_CHILDREN) > 0 ? " <Default>" : "");
}
}
void tools::print_av_option_string2(AVCodecContext* ctx_codec, std::string_view option, std::string_view text,
std::function<std::string(int64_t, std::string_view)> decoder)
{
print_av_option_string2(ctx_codec, ctx_codec, option, text, decoder);
}
void tools::print_av_option_string2(AVCodecContext* ctx_codec, void* ctx_option, std::string_view option,
std::string_view text,
std::function<std::string(int64_t, std::string_view)> decoder)
{
int64_t v = 0;
if (int err = av_opt_get_int(ctx_option, option.data(), AV_OPT_SEARCH_CHILDREN, &v); err != 0) {
DLOG_INFO("[%s] %s: <Error: %s>", ctx_codec->codec->name, text.data(), tools::get_error_description(err));
} else {
std::string name = "<Unknown>";
// Find the unit for the option.
auto* opt = av_opt_find(ctx_option, option.data(), nullptr, 0, AV_OPT_SEARCH_CHILDREN);
if (opt && opt->unit) {
for (auto* opt_test = opt; (opt_test = av_opt_next(ctx_option, opt_test)) != nullptr;) {
// Skip this entry if the unit doesn't match.
if ((opt_test->unit == nullptr) || (strcmp(opt_test->unit, opt->unit) != 0)) {
continue;
}
// Assign correct name if we found one.
if (opt_test->default_val.i64 == v) {
name = opt_test->name;
break;
}
}
if (decoder) {
name = decoder(v, name);
}
DLOG_INFO("[%s] %s: %s%s", ctx_codec->codec->name, text.data(), name.c_str(),
av_opt_is_set_to_default_by_name(ctx_option, option.data(), AV_OPT_SEARCH_CHILDREN) > 0
? " <Default>"
: "");
} else {
DLOG_INFO("[%s] %s: %" PRId64 "%s", ctx_codec->codec->name, text.data(), v,
av_opt_is_set_to_default_by_name(ctx_option, option.data(), AV_OPT_SEARCH_CHILDREN) > 0
? " <Default>"
: "");
}
}
}