Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set the codec level for AMF encoders #11548

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/obs-ffmpeg/texture-amf-opts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ static void amf_apply_opt(amf_base *enc, obs_option *opt)
if (avc)
set_avc_opt(PROFILE_LEVEL, level);
else if (hevc)
set_hevc_opt(PROFILE_LEVEL, level);
set_hevc_opt(PROFILE_LEVEL, level * 3);
else
warn("Invalid value for %s: %s", opt->name, opt->value);

Expand Down
222 changes: 217 additions & 5 deletions plugins/obs-ffmpeg/texture-amf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,82 @@ static AMFTrace *amf_trace = nullptr;
static HMODULE amf_module = nullptr;
static uint64_t amf_version = 0;

/* ========================================================================= */
/* The structure and tables below are used to determine the appropriate
* minimum encoding level for the codecs. AMF defaults to the highest
* level for each codec (AVC, HEVC, AV1), and some client devices will
* reject playback if the codec level is higher than its decode abilities,
* which make sense.
*/

struct codec_level_entry {
const char *level_str;
uint64_t max_luma_sample_rate;
uint64_t max_luma_picture_size;
amf_int64 amf_level;
};

// Ensure the table entries are ordered from lowest to highest
static std::vector<codec_level_entry> avc_levels = {{"1", (uint64_t)1485 * 256, 99 * 256, AMF_H264_LEVEL__1},
{"1.1", (uint64_t)3000 * 256, 396 * 256, AMF_H264_LEVEL__1_1},
{"1.2", (uint64_t)6000 * 256, 396 * 256, AMF_H264_LEVEL__1_2},
{"1.3", (uint64_t)11880 * 256, 396 * 256, AMF_H264_LEVEL__1_3},
{"2", (uint64_t)11880 * 256, 396 * 256, AMF_H264_LEVEL__2},
{"2.1", (uint64_t)19800 * 256, 792 * 256, AMF_H264_LEVEL__2_1},
{"2.2", (uint64_t)20250 * 256, 1620 * 256, AMF_H264_LEVEL__2_2},
{"3", (uint64_t)40500 * 256, 1620 * 256, AMF_H264_LEVEL__3},
{"3.1", (uint64_t)108000 * 256, 3600 * 256, AMF_H264_LEVEL__3_1},
{"3.2", (uint64_t)216000 * 256, 5120 * 256, AMF_H264_LEVEL__3_2},
{"4", (uint64_t)245760 * 256, 8192 * 256, AMF_H264_LEVEL__4},
{"4.1", (uint64_t)245760 * 256, 8192 * 256, AMF_H264_LEVEL__4_1},
{"4.2", (uint64_t)522240 * 256, 8704 * 256, AMF_H264_LEVEL__4_2},
{"5", (uint64_t)589824 * 256, 22080 * 256, AMF_H264_LEVEL__5},
{"5.1", (uint64_t)983040 * 256, 36864 * 256, AMF_H264_LEVEL__5_1},
{"5.2", (uint64_t)2073600 * 256, 36864 * 256, AMF_H264_LEVEL__5_2},
{"6", (uint64_t)4177920 * 256, 139264 * 256, AMF_H264_LEVEL__6},
{"6.1", (uint64_t)8355840 * 256, 139264 * 256, AMF_H264_LEVEL__6_1},
{"6.2", (uint64_t)16711680 * 256, 139264 * 256,
AMF_H264_LEVEL__6_2}};

// Ensure the table entries are ordered from lowest to highest
static std::vector<codec_level_entry> hevc_levels = {
{"1", 552960, 36864, AMF_LEVEL_1}, {"2", 3686400, 122880, AMF_LEVEL_2},
{"2.1", 7372800, 245760, AMF_LEVEL_2_1}, {"3", 16588800, 552960, AMF_LEVEL_3},
{"3.1", 33177600, 983040, AMF_LEVEL_3_1}, {"4", 66846720, 2228224, AMF_LEVEL_4},
{"4.1", 133693440, 2228224, AMF_LEVEL_4_1}, {"5", 267386880, 8912896, AMF_LEVEL_5},
{"5.1", 534773760, 8912896, AMF_LEVEL_5_1}, {"5.2", 1069547520, 8912896, AMF_LEVEL_5_2},
{"6", 1069547520, 35651584, AMF_LEVEL_6}, {"6.1", 2139095040, 35651584, AMF_LEVEL_6_1},
{"6.2", 4278190080, 35651584, AMF_LEVEL_6_2}};

/* Ensure the table entries are ordered from lowest to highest.
*
* The AV1 specification currently defines 14 levels, even though more are
* available (reserved) such as 4.3 and 7.0.
*
* AV1 defines MaxDisplayRate and MaxDecodeRate, which correspond to
* TotalDisplayLumaSampleRate and TotalDecodedLumaSampleRate, respectively,
* defined in the specification. For the table below, MaxDecodeRate is
* being used because it corresponds to all frames with show_existing_frame=0.
*
* Refer to the following for more information:
* https://github.com/AOMediaCodec/av1-spec/blob/master/annex.a.levels.md
*/
static std::vector<codec_level_entry> av1_levels = {
{"2.0", (uint64_t)5529600, 147456, AMF_VIDEO_ENCODER_AV1_LEVEL_2_0},
{"2.1", (uint64_t)10454400, 278784, AMF_VIDEO_ENCODER_AV1_LEVEL_2_1},
{"3.0", (uint64_t)24969600, 665856, AMF_VIDEO_ENCODER_AV1_LEVEL_3_0},
{"3.1", (uint64_t)39938400, 1065024, AMF_VIDEO_ENCODER_AV1_LEVEL_3_1},
{"4.0", (uint64_t)77856768, 2359296, AMF_VIDEO_ENCODER_AV1_LEVEL_4_0},
{"4.1", (uint64_t)155713536, 2359296, AMF_VIDEO_ENCODER_AV1_LEVEL_4_1},
{"5.0", (uint64_t)273715200, 8912896, AMF_VIDEO_ENCODER_AV1_LEVEL_5_0},
{"5.1", (uint64_t)547430400, 8912896, AMF_VIDEO_ENCODER_AV1_LEVEL_5_1},
{"5.2", (uint64_t)1094860800, 8912896, AMF_VIDEO_ENCODER_AV1_LEVEL_5_2},
{"5.3", (uint64_t)1176502272, 8912896, AMF_VIDEO_ENCODER_AV1_LEVEL_5_3},
{"6.0", (uint64_t)1176502272, 35651584, AMF_VIDEO_ENCODER_AV1_LEVEL_6_0},
{"6.1", (uint64_t)2189721600, 35651584, AMF_VIDEO_ENCODER_AV1_LEVEL_6_1},
{"6.2", (uint64_t)4379443200, 35651584, AMF_VIDEO_ENCODER_AV1_LEVEL_6_2},
{"6.3", (uint64_t)4706009088, 35651584, AMF_VIDEO_ENCODER_AV1_LEVEL_6_3}};

/* ========================================================================= */
/* Main Implementation */

Expand Down Expand Up @@ -1290,6 +1366,93 @@ try {
return false;
}

static void amf_set_codec_level(amf_base *enc)
{
uint64_t luma_pic_size = enc->cx * enc->cy;
uint64_t luma_sample_rate = luma_pic_size * (enc->fps_num / enc->fps_den);
std::vector<codec_level_entry> *levels;

if (enc->codec == amf_codec_type::AVC) {
levels = &avc_levels;
} else if (enc->codec == amf_codec_type::HEVC) {
levels = &hevc_levels;
} else if (enc->codec == amf_codec_type::AV1) {
levels = &av1_levels;
} else {
blog(LOG_ERROR, "%s: Unknown amf_codec_type", __FUNCTION__);
return;
}

std::vector<codec_level_entry>::const_iterator level_it = levels->begin();

/* First check if the requested sample rate and/or picture
* size is too large for the maximum level.
*/
if ((luma_sample_rate > levels->back().max_luma_sample_rate) ||
(luma_pic_size > levels->back().max_luma_picture_size)) {
/* If the calculated sample rate is greater than the highest
* value supported by the codec, clamp to the upper limit and
* log an error.
*/
level_it = --(levels->end());
blog(LOG_ERROR,
"%s: Luma sample rate %u or luma pic size %u is greater than maximum "
"allowed. Setting to level %s",
__FUNCTION__, luma_sample_rate, luma_pic_size, level_it->level_str);
} else {
/* Walk the table and find the lowest codec level
* value suitable for the given luma sample rate.
*/
while (level_it != levels->end()) {
if ((luma_sample_rate <= level_it->max_luma_sample_rate) &&
(luma_pic_size <= level_it->max_luma_picture_size)) {
break;
}
++level_it;
}
}

// Set the level for the encoder
if (enc->codec == amf_codec_type::AVC) {
set_avc_property(enc, PROFILE_LEVEL, level_it->amf_level);
} else if (enc->codec == amf_codec_type::HEVC) {
set_hevc_property(enc, PROFILE_LEVEL, level_it->amf_level);
} else if (enc->codec == amf_codec_type::AV1) {
set_av1_property(enc, LEVEL, level_it->amf_level);
}
}

static bool amf_get_level_str(amf_base *enc, amf_int64 level, char const **level_str)
{
bool found = false;
std::vector<codec_level_entry> *levels;

if (enc->codec == amf_codec_type::AVC) {
levels = &avc_levels;
} else if (enc->codec == amf_codec_type::HEVC) {
levels = &hevc_levels;
} else if (enc->codec == amf_codec_type::AV1) {
levels = &av1_levels;
} else {
blog(LOG_ERROR, "%s: Unknown amf_codec_type", __FUNCTION__);
return false;
}

for (auto level_it = levels->begin(); level_it != levels->end(); ++level_it) {
if (level == level_it->amf_level) {
found = true;
*level_str = level_it->level_str;
break;
}
}

if (!found) {
*level_str = "unknown";
}

return found;
}

static bool amf_avc_init(void *data, obs_data_t *settings)
{
amf_base *enc = (amf_base *)data;
Expand Down Expand Up @@ -1342,6 +1505,9 @@ static bool amf_avc_init(void *data, obs_data_t *settings)

set_avc_property(enc, DE_BLOCKING_FILTER, true);

// Determine and set the appropriate AVC level
amf_set_codec_level(enc);

check_preset_compatibility(enc, preset);

const char *ffmpeg_opts = obs_data_get_string(settings, "ffmpeg_opts");
Expand All @@ -1356,19 +1522,32 @@ static bool amf_avc_init(void *data, obs_data_t *settings)
if (!ffmpeg_opts || !*ffmpeg_opts)
ffmpeg_opts = "(none)";

/* The ffmpeg_opts just above may have explicitly set the AVC level to
* a value different than what was determined by amf_set_codec_level().
* Query the final AVC level then lookup the matching string. Warn if
* not found, because ffmpeg_opts is free-form and may have set something
* bogus.
*/
amf_int64 final_level;
get_avc_property(enc, PROFILE_LEVEL, &final_level);
char const *level_str = nullptr;
if (!amf_get_level_str(enc, final_level, &level_str)) {
warn("AVC level string not found. Level %d may be incorrect.", final_level);
}

info("settings:\n"
"\trate_control: %s\n"
"\tbitrate: %d\n"
"\tcqp: %d\n"
"\tkeyint: %d\n"
"\tpreset: %s\n"
"\tprofile: %s\n"
"\tlevel: %s\n"
"\tb-frames: %d\n"
"\twidth: %d\n"
"\theight: %d\n"
"\tparams: %s",
rc_str, bitrate, qp, gop_size, preset, profile, bf, enc->cx, enc->cy, ffmpeg_opts);

rc_str, bitrate, qp, gop_size, preset, profile, level_str, bf, enc->cx, enc->cy, ffmpeg_opts);
return true;
}

Expand Down Expand Up @@ -1639,6 +1818,9 @@ static bool amf_hevc_init(void *data, obs_data_t *settings)

set_hevc_property(enc, GOP_SIZE, gop_size);

// Determine and set the appropriate HEVC level
amf_set_codec_level(enc);

check_preset_compatibility(enc, preset);

const char *ffmpeg_opts = obs_data_get_string(settings, "ffmpeg_opts");
Expand All @@ -1653,17 +1835,31 @@ static bool amf_hevc_init(void *data, obs_data_t *settings)
if (!ffmpeg_opts || !*ffmpeg_opts)
ffmpeg_opts = "(none)";

/* The ffmpeg_opts just above may have explicitly set the HEVC level to
* a value different than what was determined by amf_set_codec_level().
* Query the final HEVC level then lookup the matching string. Warn if
* not found, because ffmpeg_opts is free-form and may have set something
* bogus.
*/
amf_int64 final_level;
get_hevc_property(enc, PROFILE_LEVEL, &final_level);
char const *level_str = nullptr;
if (!amf_get_level_str(enc, final_level, &level_str)) {
warn("HEVC level string not found. Level %d may be incorrect.", final_level);
}

info("settings:\n"
"\trate_control: %s\n"
"\tbitrate: %d\n"
"\tcqp: %d\n"
"\tkeyint: %d\n"
"\tpreset: %s\n"
"\tprofile: %s\n"
"\tlevel: %s\n"
"\twidth: %d\n"
"\theight: %d\n"
"\tparams: %s",
rc_str, bitrate, qp, gop_size, preset, profile, enc->cx, enc->cy, ffmpeg_opts);
rc_str, bitrate, qp, gop_size, preset, profile, level_str, enc->cx, enc->cy, ffmpeg_opts);

return true;
}
Expand Down Expand Up @@ -1977,6 +2173,9 @@ static bool amf_av1_init(void *data, obs_data_t *settings)

set_av1_property(enc, ENFORCE_HRD, true);

// Determine and set the appropriate AV1 level
amf_set_codec_level(enc);

int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec");
int gop_size = (keyint_sec) ? keyint_sec * enc->fps_num / enc->fps_den : 250;
set_av1_property(enc, GOP_SIZE, gop_size);
Expand All @@ -1990,6 +2189,19 @@ static bool amf_av1_init(void *data, obs_data_t *settings)
obs_free_options(opts);
}

/* The ffmpeg_opts just above may have explicitly set the AV1 level to
* a value different than what was determined by amf_set_codec_level().
* Query the final AV1 level then lookup the matching string. Warn if
* not found, because ffmpeg_opts is free-form and may have set something
* bogus.
*/
amf_int64 final_level;
get_av1_property(enc, LEVEL, &final_level);
char const *level_str = nullptr;
if (!amf_get_level_str(enc, final_level, &level_str)) {
warn("AV1 level string not found. Level %d may be incorrect.", final_level);
}

check_preset_compatibility(enc, preset);

if (!ffmpeg_opts || !*ffmpeg_opts)
Expand All @@ -2002,11 +2214,11 @@ static bool amf_av1_init(void *data, obs_data_t *settings)
"\tkeyint: %d\n"
"\tpreset: %s\n"
"\tprofile: %s\n"
"\tlevel: %s\n"
"\twidth: %d\n"
"\theight: %d\n"
"\tparams: %s",
rc_str, bitrate, qp, gop_size, preset, profile, enc->cx, enc->cy, ffmpeg_opts);

rc_str, bitrate, qp, gop_size, preset, profile, level_str, enc->cx, enc->cy, ffmpeg_opts);
return true;
}

Expand Down
Loading